diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..7b88c31 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +# 大目录/与镜像无关的内容不进 build context +# 注意: dist/ 和 ota-package/ 不能忽略 —— CI 预构建产物要通过 context 进入镜像 +.git +.gitea +.local +.pnpm-store +node_modules +src-tauri +docs +*.log +readme +deploy diff --git a/.env.shell b/.env.shell new file mode 100644 index 0000000..e296220 --- /dev/null +++ b/.env.shell @@ -0,0 +1,10 @@ +# Windows 外壳构建模式 (npm run build:shell / vite build --mode shell) +# 外壳内前端跑在 tauri://localhost, 没有同源后端, API 必须指向远端 Express。 +# 与 android 打包脚本 (scripts/wsl/build-apk.sh) 注入的值保持一致; +# 进程环境变量优先级高于本文件, android 流程不受影响。 +# +# 0.2.1 起正式域名: https://iboard.honor3.com (NPM 反代 -> baihu:3001, 受信证书) +# PowerSync 同步端点默认同域 (NPM 把 /sync 反代到同步服务); 若改用独立子域, +# 打包时用 VITE_POWERSYNC_URL=https://sync.example.com 覆盖即可。 +VITE_API_BASE=https://iboard.honor3.com +VITE_POWERSYNC_URL=https://iboard.honor3.com diff --git a/.gitea/workflows/android-build.yml b/.gitea/workflows/android-build.yml index 459c8ed..4340858 100644 --- a/.gitea/workflows/android-build.yml +++ b/.gitea/workflows/android-build.yml @@ -1,177 +1,190 @@ -name: Android Build & Publish - -# 仅在 android-* 分支 push 时触发 (如 android/release, android-1.0) -# 构建 release APK (keystore 签名) 并上传到 Gitea Generic Package Registry -on: - push: - branches: - - 'android-*' - -jobs: - build-android: - runs-on: ubuntu-latest - # 需要写权限: 上传 packages - permissions: - contents: read - packages: write - - env: - # 固定工具链版本, 便于 actions/cache 命中 - # Android 后端地址 (bake 进 APK); 与 scripts/wsl/build-apk.sh 保持一致 - VITE_API_BASE: http://bh.vps.honor3.com:3001 - - steps: - - name: Checkout - uses: https://gitea.com/actions/checkout@v4 - - - name: Setup Node - uses: https://gitea.com/actions/setup-node@v4 - with: - node-version: 22 - cache: 'npm' - - - name: Setup JDK 17 - uses: https://gitea.com/actions/setup-java@v4 - with: - distribution: temurin - java-version: '17' - - # ---------- 缓存: 命中后跳过下面的安装 ---------- - - name: Cache Android SDK & NDK - id: cache-android - uses: https://gitea.com/actions/cache@v4 - with: - path: ~/android-sdk - key: android-sdk-ndk-27.2-${{ runner.os }} - - - name: Cache Rust toolchain - id: cache-rust - uses: https://gitea.com/actions/cache@v4 - with: - path: | - ~/.cargo - ~/.rustup - # 工具链升级时 bump 版本号使缓存失效重装 (含 cargo-ndk) - key: rust-android-v1-${{ runner.os }} - - - name: Cache Tauri cargo target - uses: https://gitea.com/actions/cache@v4 - with: - path: src-tauri/target - key: tauri-target-android-${{ runner.os }} - - # ---------- 安装工具链 (仅缓存未命中时) ---------- - - name: Install Rust + Android targets - if: steps.cache-rust.outputs.cache-hit != 'true' - run: | - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable - echo "$HOME/.cargo/bin" >> $GITHUB_PATH - . "$HOME/.cargo/env" - rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android - cargo install cargo-ndk --locked - - - name: Install Android SDK & NDK - if: steps.cache-android.outputs.cache-hit != 'true' - run: | - export ANDROID_HOME="$HOME/android-sdk" - sudo apt-get update - sudo apt-get install -y --no-install-recommends unzip - mkdir -p "$ANDROID_HOME/cmdline-tools" - wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O /tmp/cmdline-tools.zip - unzip -q /tmp/cmdline-tools.zip -d /tmp/ - mv /tmp/cmdline-tools "$ANDROID_HOME/cmdline-tools/latest" - export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$PATH" - yes | sdkmanager --licenses >/dev/null || true - sdkmanager --install \ - "platforms;android-36" \ - "build-tools;36.0.0" \ - "platform-tools" \ - "ndk;27.2.12479018" \ - "cmake;3.22.1" - - - name: Install frontend deps - run: npm ci --no-audit --no-fund - - # ---------- 全局 Android 环境 (对后续所有 step 生效, 无论缓存是否命中) ---------- - - name: Set Android env - run: | - echo "ANDROID_HOME=$HOME/android-sdk" >> "$GITHUB_ENV" - echo "ANDROID_NDK_HOME=$HOME/android-sdk/ndk/27.2.12479018" >> "$GITHUB_ENV" - echo "$HOME/android-sdk/cmdline-tools/latest/bin" >> "$GITHUB_PATH" - echo "$HOME/android-sdk/platform-tools" >> "$GITHUB_PATH" - - # ---------- 构建 ---------- - - name: Init Android project - run: | - # tauri android init 需要 cargo (运行 cargo metadata)。 - # Rust 工具链缓存命中时 "Install Rust" 步骤被跳过, 不会把 cargo 加进全局 PATH, - # 因此这里显式加载 cargo 环境 (与 Build release APK 步骤保持一致)。 - . "$HOME/.cargo/env" - cd src-tauri - npx tauri android init --ci - - - name: Inject release signing config - run: bash scripts/ci/android-signing.sh - env: - ANDROID_KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_B64 }} - ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} - ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} - - - name: Build release APK - run: | - export ANDROID_HOME="$HOME/android-sdk" - export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/27.2.12479018" - export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH" - export CARGO_HOME="$HOME/.cargo" - . "$HOME/.cargo/env" - cd src-tauri - npx tauri android build --apk - # 断言签名配置仍在且 release 块实际引用了它 (防止 tauri 重新生成 gen/android 或正则未命中) - if ! grep -q 'create("release")' gen/android/app/build.gradle.kts || ! grep -q 'signingConfig = signingConfigs.getByName("release")' gen/android/app/build.gradle.kts; then - echo "ERROR: 构建后签名配置丢失/未生效, APK 未签名" >&2 - exit 1 - fi - - - name: Locate APK - id: apk - run: | - # 优先 universal 已签名 APK, 找不到再回退到其他已签名 APK - APK="" - UNIVERSAL=$(find src-tauri/gen/android/app/build/outputs/apk/universal/release -name '*.apk' ! -name '*unsigned*' 2>/dev/null | head -1) - if [ -n "$UNIVERSAL" ]; then - APK="$UNIVERSAL" - else - APK=$(find src-tauri/gen/android/app/build/outputs/apk -name '*.apk' ! -name '*unsigned*' | head -1) - fi - if [ -z "$APK" ]; then - echo "ERROR: 未找到已签名 APK" >&2 - exit 1 - fi - echo "path=$APK" >> $GITHUB_OUTPUT - ls -lh "$APK" - - # ---------- 上传到 Gitea Generic Package ---------- - - name: Publish APK to Gitea packages - env: - # 注意: 不能用 GITEA_TOKEN (内置 job token, 无组织 packages 写权限, 且命名规则禁止 GITEA_ 前缀) - # 用一个自定义名 secret 存放有 packages 写权限的 user token - PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }} - APK_PATH: ${{ steps.apk.outputs.path }} - run: | - set -euo pipefail - # 包版本: 用分支名去掉 android- 前缀 + 短 SHA, 保证每次 push 唯一 - BRANCH="${GITHUB_REF_NAME#android-}" - PKG_VERSION="${BRANCH}-${GITHUB_SHA:0:8}" - FILE_NAME="iboard-$(basename "$APK_PATH")" - OWNER="${GITHUB_REPOSITORY_OWNER}" - PKG_NAME="iboard" - # Gitea Generic Package Registry 上传端点 (PUT) - # 注意: GITHUB_SERVER_URL 是仓库 web 根地址, 例如 http://git.honor3.com - URL="${GITHUB_SERVER_URL}/api/packages/${OWNER}/generic/${PKG_NAME}/${PKG_VERSION}/${FILE_NAME}" - echo "上传到: ${URL}" - curl -sS --fail-with-body \ - -H "Authorization: token ${PACKAGE_TOKEN}" \ - --upload-file "$APK_PATH" \ - "${URL}" - echo "" - echo "已发布: ${URL}" +name: Android Build & Publish + +# 仅在 android-* 分支 push 时触发 (如 android/release, android-1.0) +# 构建 release APK (keystore 签名) 并上传到 Gitea Generic Package Registry +on: + push: + branches: + - 'android-*' + +# 同分支连续 push 时取消旧的构建, 省资源也避免上传过时 APK +concurrency: + group: android-${{ github.ref_name }} + cancel-in-progress: true + +jobs: + build-android: + runs-on: ubuntu-latest + # 需要写权限: 上传 packages + permissions: + contents: read + packages: write + + env: + # 固定工具链版本, 便于 actions/cache 命中 + # Android 后端地址 (bake 进 APK); 与 scripts/wsl/build-apk.sh 保持一致 + VITE_API_BASE: https://iboard.honor3.com + + steps: + - name: Checkout + uses: https://gitea.com/actions/checkout@v4 + + - name: Setup Node + uses: https://gitea.com/actions/setup-node@v4 + with: + node-version: 22 + cache: 'npm' + + # 版本号一致性: package.json / tauri.conf.json / Cargo.toml / Cargo.lock 必须完全相等。 + # tauri.conf.json 的 version 就是 APK 的 versionName, 漏改会打出一个版本号错误的安装包。 + - name: Check version consistency + run: node scripts/ci/check-version.mjs + + - name: Setup JDK 17 + uses: https://gitea.com/actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' + + # ---------- 缓存: 命中后跳过下面的安装 ---------- + - name: Cache Android SDK & NDK + id: cache-android + uses: https://gitea.com/actions/cache@v4 + with: + path: ~/android-sdk + key: android-sdk-ndk-27.2-${{ runner.os }} + + - name: Cache Rust toolchain + id: cache-rust + uses: https://gitea.com/actions/cache@v4 + with: + path: | + ~/.cargo + ~/.rustup/toolchains + # 工具链升级时 bump 版本号使缓存失效重装 (含 cargo-ndk) + key: rust-android-v1-${{ runner.os }} + + - name: Cache Tauri cargo target + uses: https://gitea.com/actions/cache@v4 + with: + path: src-tauri/target + # 依赖变化 → 新 key; 命不中时回退最近一次的 target 目录做增量编译 + key: tauri-target-android-${{ hashFiles('src-tauri/Cargo.lock') }} + restore-keys: tauri-target-android- + + # ---------- 安装工具链 (仅缓存未命中时) ---------- + - name: Install Rust + Android targets + if: steps.cache-rust.outputs.cache-hit != 'true' + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable + echo "$HOME/.cargo/bin" >> $GITHUB_PATH + . "$HOME/.cargo/env" + # 只构建 aarch64 (现代 Android 手机); 需要模拟器/x86 设备时再加回其他 target + rustup target add aarch64-linux-android + cargo install cargo-ndk --locked + + - name: Install Android SDK & NDK + if: steps.cache-android.outputs.cache-hit != 'true' + run: | + export ANDROID_HOME="$HOME/android-sdk" + sudo apt-get update + sudo apt-get install -y --no-install-recommends unzip + mkdir -p "$ANDROID_HOME/cmdline-tools" + wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O /tmp/cmdline-tools.zip + unzip -q /tmp/cmdline-tools.zip -d /tmp/ + mv /tmp/cmdline-tools "$ANDROID_HOME/cmdline-tools/latest" + export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$PATH" + yes | sdkmanager --licenses >/dev/null || true + sdkmanager --install \ + "platforms;android-36" \ + "build-tools;36.0.0" \ + "platform-tools" \ + "ndk;27.2.12479018" \ + "cmake;3.22.1" + + - name: Install frontend deps + run: npm ci --no-audit --no-fund + + # ---------- 全局 Android 环境 (对后续所有 step 生效, 无论缓存是否命中) ---------- + - name: Set Android env + run: | + echo "ANDROID_HOME=$HOME/android-sdk" >> "$GITHUB_ENV" + echo "ANDROID_NDK_HOME=$HOME/android-sdk/ndk/27.2.12479018" >> "$GITHUB_ENV" + echo "$HOME/android-sdk/cmdline-tools/latest/bin" >> "$GITHUB_PATH" + echo "$HOME/android-sdk/platform-tools" >> "$GITHUB_PATH" + + # ---------- 构建 ---------- + - name: Init Android project + run: | + # tauri android init 需要 cargo (运行 cargo metadata)。 + # Rust 工具链缓存命中时 "Install Rust" 步骤被跳过, 不会把 cargo 加进全局 PATH, + # 因此这里显式加载 cargo 环境 (与 Build release APK 步骤保持一致)。 + . "$HOME/.cargo/env" + cd src-tauri + npx tauri android init --ci + + - name: Inject release signing config + run: bash scripts/ci/android-signing.sh + env: + ANDROID_KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_B64 }} + ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }} + ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }} + + - name: Build release APK + run: | + export ANDROID_HOME="$HOME/android-sdk" + export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/27.2.12479018" + export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin:$PATH" + export CARGO_HOME="$HOME/.cargo" + . "$HOME/.cargo/env" + cd src-tauri + npx tauri android build --apk --target aarch64 + # 断言签名配置仍在且 release 块实际引用了它 (防止 tauri 重新生成 gen/android 或正则未命中) + if ! grep -q 'create("release")' gen/android/app/build.gradle.kts || ! grep -q 'signingConfig = signingConfigs.getByName("release")' gen/android/app/build.gradle.kts; then + echo "ERROR: 构建后签名配置丢失/未生效, APK 未签名" >&2 + exit 1 + fi + + - name: Locate APK + id: apk + run: | + # 优先 universal 已签名 APK, 找不到再回退到其他已签名 APK + APK="" + UNIVERSAL=$(find src-tauri/gen/android/app/build/outputs/apk/universal/release -name '*.apk' ! -name '*unsigned*' 2>/dev/null | head -1) + if [ -n "$UNIVERSAL" ]; then + APK="$UNIVERSAL" + else + APK=$(find src-tauri/gen/android/app/build/outputs/apk -name '*.apk' ! -name '*unsigned*' | head -1) + fi + if [ -z "$APK" ]; then + echo "ERROR: 未找到已签名 APK" >&2 + exit 1 + fi + echo "path=$APK" >> $GITHUB_OUTPUT + ls -lh "$APK" + + # ---------- 上传到 Gitea Generic Package ---------- + - name: Publish APK to Gitea packages + env: + # 注意: 不能用 GITEA_TOKEN (内置 job token, 无组织 packages 写权限, 且命名规则禁止 GITEA_ 前缀) + # 用一个自定义名 secret 存放有 packages 写权限的 user token + PACKAGE_TOKEN: ${{ secrets.PACKAGE_TOKEN }} + APK_PATH: ${{ steps.apk.outputs.path }} + run: | + set -euo pipefail + # 包版本: 用分支名去掉 android- 前缀 + 短 SHA, 保证每次 push 唯一 + BRANCH="${GITHUB_REF_NAME#android-}" + PKG_VERSION="${BRANCH}-${GITHUB_SHA:0:8}" + FILE_NAME="iboard-$(basename "$APK_PATH")" + OWNER="${GITHUB_REPOSITORY_OWNER}" + PKG_NAME="iboard" + # Gitea Generic Package Registry 上传端点 (PUT) + # 注意: GITHUB_SERVER_URL 是仓库 web 根地址, 例如 http://git.honor3.com + URL="${GITHUB_SERVER_URL}/api/packages/${OWNER}/generic/${PKG_NAME}/${PKG_VERSION}/${FILE_NAME}" + echo "上传到: ${URL}" + curl -sS --fail-with-body \ + -H "Authorization: token ${PACKAGE_TOKEN}" \ + --upload-file "$APK_PATH" \ + "${URL}" + echo "" + echo "已发布: ${URL}" diff --git a/.gitea/workflows/docker-build.yml b/.gitea/workflows/docker-build.yml index 06cef12..a68b2eb 100644 --- a/.gitea/workflows/docker-build.yml +++ b/.gitea/workflows/docker-build.yml @@ -1,84 +1,149 @@ -name: Docker Build and Push - -# 仅 main 分支 push 触发, 其他分支不触发 -# 每次主分支提交: 构建前端 + 生成 OTA 升级包 (签名) → 打进 Docker 镜像 → 部署 -# OTA 随镜像发布: 容器内 OTA_PACKAGE_DIR=/app/ota-package 指向打包的 ota-package/ -on: - push: - branches: - - main - - master - -jobs: - build-image: - runs-on: ubuntu-latest - env: - # 关键: 显式固定 Android/OTA 后端地址, 覆盖 runner 环境里可能存在的 - # 其他 VITE_API_BASE 值。否则 OTA 包里的前端可能连错后端, 导致 - # "OTA 更新后登录失败/进不去内部"。与 android-build.yml 保持一致。 - VITE_API_BASE: http://bh.vps.honor3.com:3001 - steps: - - uses: https://gitea.com/actions/checkout@v4 - - - name: Setup Node - uses: https://gitea.com/actions/setup-node@v4 - with: - node-version: 22 - - - name: Install deps - run: npm ci --no-audit --no-fund - - - name: Build frontend - # dist/ 供 OTA 打包用 (Dockerfile 内也会构建一份) - run: npm run build - - - name: Install minisign - run: sudo apt-get update && sudo apt-get install -y minisign - - - name: Build & sign OTA package - # 每次主分支提交生成 OTA 升级包, sequence 默认时间戳 (单调递增) - # 产物 ota-package/ (tar.gz + latest.json + .minisig) 进入 docker build context, 由 Dockerfile COPY 进镜像 - env: - MINISIGN_PRIVATE_KEY: ${{ secrets.MINISIGN_PRIVATE_KEY }} - OTA_NOTES: ${{ github.sha }} - run: | - set -euo pipefail - node scripts/build-ota.mjs --out ota-package --notes ${OTA_NOTES} - echo "===== latest.json =====" - cat ota-package/latest.json - echo "===== bundle sha256 =====" - sha256sum ota-package/iboard-ota.tar.gz - - - name: Build Docker image - # DATABASE_URL 仅在 prisma generate 阶段需要, 可用任何合法占位串 - run: docker build -t iboard:latest --build-arg DATABASE_URL=mysql://build:build@127.0.0.1:3306/build . - - - name: List images - run: docker images - - - name: Apply DB migrations - # 仓库内的 prisma 迁移自动应用 (migrate deploy 幂等, 无新迁移时为 no-op) - env: - DATABASE_URL: ${{ secrets.DATABASE_URL }} - run: npx prisma migrate deploy - - - name: Remove old container - run: docker rm -vf iboard || true - - - name: Recycle container - run: docker system prune --volumes -f - - - name: Run container - # DATABASE_URL 通过 gitea Actions secret 注入, 不进镜像层 - # OTA_PACKAGE_DIR 已在 Dockerfile 设为 /app/ota-package - # 米家凭证 (auth.txt) 不进 git: 运维放宿主机 /app/iboard/cache, bind-mount 进容器 (MIHOME_CACHE_DIR=/app/cache) - run: | - docker run -d --name iboard -p 3001:3001 \ - -e DATABASE_URL="${{ secrets.DATABASE_URL }}" \ - -e JWT_SECRET="${{ secrets.JWT_SECRET }}" \ - -e MIHOME_CACHE_DIR=/app/cache \ - -v /app/iboard/cache:/app/cache \ - iboard:latest - env: - DATABASE_URL: ${{ secrets.DATABASE_URL }} - JWT_SECRET: ${{ secrets.JWT_SECRET }} +name: Docker Build and Push + +# 仅主分支 (master) push 触发部署; 其他分支一律不触发 (见 readme「分支规则」) +# 每次提交: 构建前端 + 生成 OTA 升级包 (签名) → 打进 Docker 镜像 → 自动升级数据库 → 部署 +# OTA 随镜像发布: 容器内 OTA_PACKAGE_DIR=/app/ota-package 指向打包的 ota-package/ +# +# 分支策略 (2026-10 调整): +# - master = 主分支, 唯一部署出口, push 即发布生产 (容器 iboard, 宿主 3001) +# - dev-* = 开发集成分支, 只做 CI 校验 (构建/版本一致性), **不再自动部署**; +# 要上线必须先合并到 master 再推 master +# - tmp-* = 单个问题的临时分支, 合到 dev-* 后删除 +# - android-*= 只用于触发 APK 外壳构建, 不部署; 改动前须与 master 同步 +# +# 历史: 之前 dev-* 与 master 共用同一套生产环境 (推送即发布), 容易把半成品带上线, +# 现已取消 dev-* 的 push 触发。 +on: + push: + branches: + - master + +# 连续 push master 时取消还在跑的旧部署, 避免排队执行过时版本的完整部署 +concurrency: + group: deploy-master + cancel-in-progress: true + +jobs: + build-image: + runs-on: ubuntu-latest + env: + # 关键: 显式固定 Android/OTA 后端地址, 覆盖 runner 环境里可能存在的 + # 其他 VITE_API_BASE 值。否则 OTA 包里的前端可能连错后端, 导致 + # "OTA 更新后登录失败/进不去内部"。与 android-build.yml 保持一致。 + VITE_API_BASE: https://iboard.honor3.com + # OTA bundle 要求的最低原生二进制版本。 + # 与 package.json 的应用版本是**两个概念**: 前端 OTA 不碰原生层, + # 现有 APK 仍是 0.1.0, 所以这里固定 0.1.0 (否则旧 APK 会被判定为不兼容而收不到更新)。 + # 重打 APK 并确认原生层无破坏后, 再提升此值。 + OTA_MIN_BINARY: '0.1.0' + steps: + - uses: https://gitea.com/actions/checkout@v4 + + - name: Setup Node + uses: https://gitea.com/actions/setup-node@v4 + with: + node-version: 22 + cache: 'npm' + + - name: Check version consistency & branch + # 1) package.json / tauri.conf.json / Cargo.toml / Cargo.lock 四处版本必须完全相等且不带 -ota + # (tauri.conf.json 的 version 就是 APK 的 versionName) + # 2) 防止 OTA 版本号与分支错位: OTA 包的 version 直接取自 package.json, + # dev-0.2 分支上必须是 0.2.x, 否则线上 OTA 会报成上一个版本的号 + # (master 分支跳过分支前缀校验, 但四处一致性照样强制) + env: + GITHUB_REF_NAME: ${{ github.ref_name }} + run: node scripts/ci/check-version.mjs + + - name: Install deps + run: npm ci --no-audit --no-fund + + - name: Build frontend + # 全流程唯一一次前端构建: dist/ 既供 OTA 打包, 也通过 build context 进入镜像 + # (docker build 传 PREBUILT_DIST=true 跳过镜像内的重复构建) + run: npm run build + + - name: Install minisign + # 静态二进制直接下载, 免去每次 apt-get update + install + # 注意: release tarball 是 macOS 上打包的, 混有多个平台的 minisign 二进制, + # 逐个试运行挑出本机可执行的, 都不行再回退 apt + run: | + set -euo pipefail + mkdir -p /tmp/ms + curl -sL https://github.com/jedisct1/minisign/releases/download/0.12/minisign-0.12-linux.tar.gz | tar xz -C /tmp/ms + BIN="" + for c in $(find /tmp/ms -type f -name minisign); do + if "$c" -v >/dev/null 2>&1; then BIN="$c"; break; fi + done + if [ -n "$BIN" ]; then + sudo install -m755 "$BIN" /usr/local/bin/minisign + else + echo "未找到可执行的静态二进制, 回退 apt 安装" + sudo apt-get update && sudo apt-get install -y minisign + fi + minisign -v + + - name: Build & sign OTA package + # 每次主分支提交生成 OTA 升级包, sequence 默认时间戳 (单调递增) + # 发布说明 = 触发构建的 commit message (标题+正文; 打包脚本内安全截断 400 字符), + # commit sha 通过 --commit 单独写入 manifest, 前端分行展示 + # 产物 ota-package/ (tar.gz + latest.json + .minisig) 进入 docker build context, 由 Dockerfile COPY 进镜像 + env: + MINISIGN_PRIVATE_KEY: ${{ secrets.MINISIGN_PRIVATE_KEY }} + run: | + set -euo pipefail + # 不用 head -c 截断: 那会把多字节汉字切成非法 UTF-8, latest.json 解析失败 + MSG="$(git log -1 --pretty=format:%B | sed -e '/^[[:space:]]*$/d')" + echo "notes: ${MSG}" + node scripts/build-ota.mjs --out ota-package --notes "${MSG}" --commit "${GITHUB_SHA}" --min-binary ${OTA_MIN_BINARY} + echo "===== latest.json =====" + cat ota-package/latest.json + echo "===== bundle sha256 =====" + sha256sum ota-package/iboard-ota.tar.gz + + - name: Build Docker image + # DATABASE_URL 仅在 prisma generate 阶段需要, 可用任何合法占位串 + # PREBUILT_DIST=true: 复用上一步构建好的 dist/, 镜像内跳过前端构建 + # DOCKER_BUILDKIT=1: Dockerfile 用了 cache mount 语法 + run: docker build -t iboard:latest --build-arg DATABASE_URL=mysql://build:build@127.0.0.1:3306/build --build-arg PREBUILT_DIST=true . + env: + DOCKER_BUILDKIT: '1' + + - name: List images + run: docker images + + - name: Apply DB migrations + # 数据库自动升级: 按 prisma/migrations/ 下的迁移顺序应用 (生产库由 master 部署时统一升级) + # migrate deploy 幂等, 无新迁移时为 no-op + # 位置关键: 在 "Remove old container" 之前 —— 建表/改表成功后才切换流量, + # 迁移失败则整个 workflow 中断, 不会用新镜像顶掉仍在运行的旧容器 + env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} + run: | + set -euo pipefail + npx prisma migrate status || true + npx prisma migrate deploy + + - name: Remove old container + run: docker rm -vf iboard || true + + - name: Prune dangling images + # 只清理本项目的悬空镜像 (旧 iboard:latest 被同名新镜像顶掉后变 dangling), + # 不再用 `docker system prune --volumes` —— 那会清光宿主机 build cache, + # 导致每次部署都全量重装 npm 依赖、重跑构建 + run: docker image prune -f + + - name: Run container + # DATABASE_URL 通过 gitea Actions secret 注入, 不进镜像层 + # OTA_PACKAGE_DIR 已在 Dockerfile 设为 /app/ota-package + # 米家凭证 (auth.txt) 不进 git: 运维放宿主机 /app/iboard/cache, bind-mount 进容器 (MIHOME_CACHE_DIR=/app/cache) + run: | + docker run -d --name iboard -p 3001:3001 \ + -e DATABASE_URL="${{ secrets.DATABASE_URL }}" \ + -e JWT_SECRET="${{ secrets.JWT_SECRET }}" \ + -e MIHOME_CACHE_DIR=/app/cache \ + -v /app/iboard/cache:/app/cache \ + iboard:latest + env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} + JWT_SECRET: ${{ secrets.JWT_SECRET }} diff --git a/.gitea/workflows/ota-build.yml b/.gitea/workflows/ota-build.yml index d293808..6bcc46f 100644 --- a/.gitea/workflows/ota-build.yml +++ b/.gitea/workflows/ota-build.yml @@ -1,75 +1,97 @@ -name: OTA Frontend Build & Sign - -# 前端 OTA 升级包构建 + 签名 + 上传 artifact -# 触发: 仅手动 (workflow_dispatch), 发版前用 -# - 主分支 push 的自动 OTA 生成已并入 docker-build.yml (随镜像发布), 本 workflow 不再监听 push 避免重复构建 -# 输入: sequence / notes / min_binary (可覆盖默认) -# Secret 依赖: MINISIGN_PRIVATE_KEY (整段 .key 文件内容, 含首行 untrusted comment) -# 注意: 此 workflow 不会修改 src-tauri/tauri.conf.json 里的 pubkey, -# 换密钥请手动改 conf 并 commit. - -on: - workflow_dispatch: - inputs: - sequence: - description: 'OTA sequence (单调递增整数, 留空用 timestamp)' - required: false - type: string - notes: - description: '发布说明 (写入 latest.json.notes)' - required: false - type: string - default: '' - min_binary: - description: '最低 binary 版本 (留空用 src-tauri/tauri.conf.json 的 version)' - required: false - type: string - default: '' - -jobs: - build-ota: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: https://gitea.com/actions/checkout@v4 - - - name: Setup Node - uses: https://gitea.com/actions/setup-node@v4 - with: - node-version: 22 - - - name: Install deps - run: npm ci --no-audit --no-fund - - - name: Build frontend - # 复用 vite build 产物; 不打 Docker (本 workflow 只关心前端) - run: npm run build - - - name: Install minisign - run: sudo apt-get update && sudo apt-get install -y minisign - - - name: Build & sign OTA package - env: - MINISIGN_PRIVATE_KEY: ${{ secrets.MINISIGN_PRIVATE_KEY }} - OTA_NOTES: ${{ inputs.notes }} - run: | - set -euo pipefail - OUT="ota-package" - ARGS="--out ${OUT}" - # inputs 留空时 → 不传 --sequence/--min-binary, 脚本默认 sequence=时间戳, min-binary=tauri.conf version - if [ -n "${{ inputs.sequence }}" ]; then ARGS="${ARGS} --sequence ${{ inputs.sequence }}"; fi - if [ -n "${OTA_NOTES}" ]; then ARGS="${ARGS} --notes ${OTA_NOTES}"; fi - if [ -n "${{ inputs.min_binary }}" ]; then ARGS="${ARGS} --min-binary ${{ inputs.min_binary }}"; fi - node scripts/build-ota.mjs ${ARGS} - echo "===== latest.json =====" - cat ${OUT}/latest.json - echo "===== bundle sha256 =====" - sha256sum ${OUT}/iboard-ota.tar.gz - - - name: Upload OTA package as artifact - uses: https://gitea.com/actions/upload-artifact@v4 - with: - name: iboard-ota-${{ inputs.sequence || github.run_number }} - path: ota-package/ - retention-days: 30 - if-no-files-found: error +name: OTA Frontend Build & Sign + +# 前端 OTA 升级包构建 + 签名 + 上传 artifact +# 触发: 仅手动 (workflow_dispatch), 发版前用 +# - 主分支 push 的自动 OTA 生成已并入 docker-build.yml (随镜像发布), 本 workflow 不再监听 push 避免重复构建 +# 输入: sequence / notes / min_binary (可覆盖默认) +# Secret 依赖: MINISIGN_PRIVATE_KEY (整段 .key 文件内容, 含首行 untrusted comment) +# 注意: 此 workflow 不会修改 src-tauri/tauri.conf.json 里的 pubkey, +# 换密钥请手动改 conf 并 commit. + +on: + workflow_dispatch: + inputs: + sequence: + description: 'OTA sequence (单调递增整数, 留空用 timestamp)' + required: false + type: string + notes: + description: '发布说明 (写入 latest.json.notes)' + required: false + type: string + default: '' + min_binary: + description: '最低 binary 版本 (默认 0.1.0 = 现有 APK 的原生版本, 与前端应用版本无关)' + required: false + type: string + default: '0.1.0' + +jobs: + build-ota: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: https://gitea.com/actions/checkout@v4 + + - name: Setup Node + uses: https://gitea.com/actions/setup-node@v4 + with: + node-version: 22 + + - name: Install deps + run: npm ci --no-audit --no-fund + + - name: Build frontend + # 复用 vite build 产物; 不打 Docker (本 workflow 只关心前端) + run: npm run build + + - name: Install minisign + # 静态二进制直接下载, 免去每次 apt-get update + install + # 注意: release tarball 是 macOS 上打包的, 混有多个平台的 minisign 二进制, + # 逐个试运行挑出本机可执行的, 都不行再回退 apt + run: | + set -euo pipefail + mkdir -p /tmp/ms + curl -sL https://github.com/jedisct1/minisign/releases/download/0.12/minisign-0.12-linux.tar.gz | tar xz -C /tmp/ms + BIN="" + for c in $(find /tmp/ms -type f -name minisign); do + if "$c" -v >/dev/null 2>&1; then BIN="$c"; break; fi + done + if [ -n "$BIN" ]; then + sudo install -m755 "$BIN" /usr/local/bin/minisign + else + echo "未找到可执行的静态二进制, 回退 apt 安装" + sudo apt-get update && sudo apt-get install -y minisign + fi + minisign -v + + - name: Build & sign OTA package + # 发布说明默认 = 检出 commit 的 message (标题+正文), commit sha 单独写入; + # 手动填写的 notes 优先 + env: + MINISIGN_PRIVATE_KEY: ${{ secrets.MINISIGN_PRIVATE_KEY }} + run: | + set -euo pipefail + OUT="ota-package" + ARGS="--out ${OUT} --commit ${GITHUB_SHA}" + if [ -n "${{ inputs.sequence }}" ]; then ARGS="${ARGS} --sequence ${{ inputs.sequence }}"; fi + if [ -n "${{ inputs.notes }}" ]; then + ARGS="${ARGS} --notes \"${{ inputs.notes }}\"" + else + MSG="$(git log -1 --pretty=format:%B | sed -e '/^[[:space:]]*$/d')" + ARGS="${ARGS} --notes \"${MSG}\"" + fi + if [ -n "${{ inputs.min_binary }}" ]; then ARGS="${ARGS} --min-binary ${{ inputs.min_binary }}"; fi + eval "node scripts/build-ota.mjs ${ARGS}" + echo "===== latest.json =====" + cat ${OUT}/latest.json + echo "===== bundle sha256 =====" + sha256sum ${OUT}/iboard-ota.tar.gz + + - name: Upload OTA package as artifact + uses: https://gitea.com/actions/upload-artifact@v4 + with: + name: iboard-ota-${{ inputs.sequence || github.run_number }} + path: ota-package/ + retention-days: 30 + if-no-files-found: error diff --git a/.gitignore b/.gitignore index 9edb0b1..e9a0913 100644 --- a/.gitignore +++ b/.gitignore @@ -1,47 +1,64 @@ -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build -/out -/.next - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -# Editor directories and files -.idea -.vscode -*.swp -*.swo -*~ -dist/* - -# local env (contains API keys) -/.env -.reasonix/* -# OTA 升级: 产物 (运行时生成, 部署时单独管理) + 私钥 (绝不能提交) -ota-package/* -!ota-package/README.md +# dependencies +/node_modules +/.pnp +.pnp.js -# apizero 文件缓存 (运行时按需生成) -cache/apizero/ -# Dockerfile 持久卷挂载点 (跟 ota-package 同类) -.cache/ -# 米家凭证缓存 (含 token, 绝不能提交) +# testing +/coverage + +# production +/build +/out +/.next + +# misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Editor directories and files +.idea +.vscode +*.swp +*.swo +*~ +dist/* + +# local env (contains API keys) +/.env +.reasonix/* +# OTA 升级: 产物 (运行时生成, 部署时单独管理) + 私钥 (绝不能提交) +ota-package/* +!ota-package/README.md + +# apizero 文件缓存 (运行时按需生成) +cache/apizero/ +# Dockerfile 持久卷挂载点 (跟 ota-package 同类) +.cache/ +# 米家凭证缓存 (含 token, 绝不能提交) server/mihome/.cache/ +# 工作区记忆/会话数据 (本地私有, 不入库) +.workbuddy/ + +# 本地 pnpm 安装产物 (项目使用 package-lock.json) +/.pnpm-store + +# 0.2.1 验证部署: 服务器本地数据卷与密钥 (绝不入库) +deploy/0.2.1/data/ +deploy/0.2.1/.env + +# ¹¹½¨/¹¤¾ßÁÙʱĿ¼ (local) +.esbuild-tmp/ + + +# Windows ´ò°ü²úÎï (±¾µØÉú³É, ²»Èë¿â) +/artifacts/ + diff --git a/.local/cdp-drag-test.mjs b/.local/cdp-drag-test.mjs new file mode 100644 index 0000000..a545c8d --- /dev/null +++ b/.local/cdp-drag-test.mjs @@ -0,0 +1,78 @@ +// CDP 触摸手势驱动: 长按 400ms 激活 + 水平拖动, 观察 useRowDrag 行为 +import WebSocket from 'ws' +import { spawn } from 'node:child_process' + +const C = JSON.parse((await (await fetch('http://127.0.0.1:9223/json/version')).text())) +const ws = new WebSocket(C.webSocketDebuggerUrl, { perMessageDeflate: false }) +let id = 0 +const pending = new Map() +const send = (method, params = {}, sessionId) => new Promise((res) => { + const i = ++id + pending.set(i, res) + ws.send(JSON.stringify({ id: i, method, params, sessionId })) +}) +ws.on('message', (d) => { + const m = JSON.parse(d) + if (m.id && pending.has(m.id)) { pending.get(m.id)(m.result); pending.delete(m.id) } + if (m.method === 'Runtime.consoleAPICalled') { + console.log('[console]', m.params.type, m.params.args.map((a) => a.value ?? a.description).join(' ')) + } +}) +await new Promise((r) => ws.on('open', r)) + +const { targetId } = await send('Target.createTarget', { url: 'about:blank' }) +const { sessionId } = await send('Target.attachToTarget', { targetId, flatten: true }) +const S = (m, p) => send(m, p, sessionId) +await S('Runtime.enable') +await S('Page.enable') +await S('Emulation.setDeviceMetricsOverride', { width: 400, height: 800, deviceScaleFactor: 2, mobile: true }) +await S('Emulation.setTouchEmulationEnabled', { enabled: true, maxTouchPoints: 5 }) +await S('Page.navigate', { url: 'http://127.0.0.1:5199/dragtest.html' }) +await new Promise((r) => setTimeout(r, 3000)) + +const sleep = (ms) => new Promise((res) => setTimeout(res, ms)) +const ev = async (expr) => (await S('Runtime.evaluate', { expression: expr, returnByValue: true })).result.value +let rect = null +for (let i = 0; i < 30 && !rect; i++) { + await sleep(1000) + const v = await ev(`(() => { const el = document.querySelector('[data-task-id="3"]'); return el ? JSON.stringify(el.getBoundingClientRect()) : null })()`) + if (v) rect = JSON.parse(v) +} +if (!rect) { console.log('page never loaded'); ws.close(); process.exit(1) } +console.log('row rect', JSON.stringify(rect)) + +const r = rect +const x0 = r.left + r.width / 2 +const y0 = r.top + r.height / 2 + +const touch = (type, x, y) => S('Input.dispatchTouchEvent', { + type, + touchPoints: type === 'touchEnd' ? [] : [{ x, y, id: 1 }], +}) + +// 1) touchStart + 按住 500ms (激活长按) +await touch('touchStart', x0, y0) +console.log('--- touchStart, holding 500ms') +await sleep(500) + +// 2) 水平拖动 +200px, 每步 +25px / 40ms (纯水平) +for (let dx = 25; dx <= 200; dx += 25) { + await touch('touchMove', x0 + dx, y0) + await sleep(40) +} +console.log('--- moved to +200px (pure horizontal)') + +// 3) 带纵向分量的拖动 (模拟真实手指): +160px 横 / +40px 纵 +await touch('touchMove', x0 + 60, y0 + 12) +await sleep(40) +await touch('touchMove', x0 + 120, y0 + 26) +await sleep(40) +await touch('touchMove', x0 + 160, y0 + 40) +await sleep(40) + +await touch('touchEnd') +await sleep(600) + +console.log('--- events on page:') +console.log((await ev('window.__ev.join("\\n")'))) +ws.close() diff --git a/.local/cdp-drag-test2.mjs b/.local/cdp-drag-test2.mjs new file mode 100644 index 0000000..e5630c8 --- /dev/null +++ b/.local/cdp-drag-test2.mjs @@ -0,0 +1,77 @@ +// CDP 触摸手势驱动 v2: 把手立即拖动 + 长按路径, 组内/跨组落点 +import WebSocket from 'ws' + +const C = JSON.parse((await (await fetch('http://127.0.0.1:9223/json/version')).text())) +const ws = new WebSocket(C.webSocketDebuggerUrl, { perMessageDeflate: false }) +let id = 0 +const pending = new Map() +const send = (method, params = {}, sessionId) => new Promise((res) => { + const i = ++id + pending.set(i, res) + ws.send(JSON.stringify({ id: i, method, params, sessionId })) +}) +ws.on('message', (d) => { + const m = JSON.parse(d) + if (m.id && pending.has(m.id)) { pending.get(m.id)(m.result); pending.delete(m.id) } +}) +await new Promise((r) => ws.on('open', r)) + +const { targetId } = await send('Target.createTarget', { url: 'about:blank' }) +const { sessionId } = await send('Target.attachToTarget', { targetId, flatten: true }) +const S = (m, p) => send(m, p, sessionId) +await S('Runtime.enable'); await S('Page.enable') +await S('Emulation.setDeviceMetricsOverride', { width: 400, height: 800, deviceScaleFactor: 2, mobile: true }) +await S('Emulation.setTouchEmulationEnabled', { enabled: true, maxTouchPoints: 5 }) +await S('Page.navigate', { url: 'http://127.0.0.1:5199/dragtest.html' }) + +const sleep = (ms) => new Promise((res) => setTimeout(res, ms)) +const ev = async (expr) => (await S('Runtime.evaluate', { expression: expr, returnByValue: true })).result.value + +const center = async (sel) => { + const r = JSON.parse(await ev(`JSON.stringify(document.querySelector('${sel}').getBoundingClientRect())`)) + return { x: r.left + r.width / 2, y: r.top + r.height / 2 } +} +for (let i = 0; i < 30; i++) { await sleep(1000); if (await ev(`!!document.querySelector('[data-habit-id="1"]')`)) break } +const touch = (type, x, y) => S('Input.dispatchTouchEvent', { + type, touchPoints: type === 'touchEnd' ? [] : [{ x, y, id: 1 }], +}) + +// 用例 1: 按住 habit-1 的把手, 立即垂直拖到 habit-4 中部, 松手 → 应 DROP before/after 4 +let p = await center('[data-habit-id="1"] .grip') +await touch('touchStart', p.x, p.y) +await sleep(60) +const t = await center('[data-habit-id="4"]') +await touch('touchMove', p.x, p.y + 20) +await sleep(30) +await touch('touchMove', t.x, t.y) +await sleep(60) +const cls1 = await ev(`document.querySelector('[data-habit-id="4"]').className`) +console.log('case1 target class:', cls1) +await touch('touchEnd') +await sleep(300) + +// 用例 2: 按住 habit-1 把手拖到每周组 (habit-5) → 跨组, 应无落点无 DROP +p = await center('[data-habit-id="1"] .grip') +await touch('touchStart', p.x, p.y) +await sleep(60) +const w = await center('[data-habit-id="5"]') +await touch('touchMove', w.x, w.y) +await sleep(60) +const cls2 = await ev(`document.querySelector('[data-habit-id="5"]').className`) +console.log('case2 target class (应无 dragover):', cls2) +await touch('touchEnd') +await sleep(300) + +// 用例 3: 长按卡片主体 500ms 后拖到 habit-3 下半 → DROP after 3 +p = await center('[data-habit-id="2"]') +await touch('touchStart', p.x, p.y) +await sleep(520) +const t3 = await center('[data-habit-id="3"]') +await touch('touchMove', t3.x, t3.y + 20) +await sleep(60) +await touch('touchEnd') +await sleep(300) + +console.log('--- events:') +console.log(await ev('window.__ev.join("\\n")')) +ws.close() diff --git a/.local/cdp-lp.mjs b/.local/cdp-lp.mjs new file mode 100644 index 0000000..ba33313 --- /dev/null +++ b/.local/cdp-lp.mjs @@ -0,0 +1,27 @@ +import WebSocket from 'ws' +const C = JSON.parse((await (await fetch('http://127.0.0.1:9223/json/version')).text())) +const ws = new WebSocket(C.webSocketDebuggerUrl, { perMessageDeflate: false }) +let id = 0; const pending = new Map() +const send = (m, p = {}, s) => new Promise((r) => { const i = ++id; pending.set(i, r); ws.send(JSON.stringify({ id: i, method: m, params: p, sessionId: s })) }) +ws.on('message', (d) => { const m = JSON.parse(d); if (m.id && pending.has(m.id)) { pending.get(m.id)(m.result); pending.delete(m.id) } }) +await new Promise((r) => ws.on('open', r)) +const targets = await send('Target.getTargets') +const t = targets.targetInfos.find((x) => x.url.includes('dragtest')) +const { sessionId } = await send('Target.attachToTarget', { targetId: t.targetId, flatten: true }) +const S = (m, p) => send(m, p, sessionId) +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) +const ev = async (e) => (await S('Runtime.evaluate', { expression: e, returnByValue: true })).result.value +// 长按 habit-2 主体, 分别在 450ms 后检查激活态, 再拖动 +const r = JSON.parse(await ev(`JSON.stringify(document.querySelector('[data-habit-id="2"]').getBoundingClientRect())`)) +const x = r.left + r.width / 2, y = r.top + r.height / 2 +await S('Input.dispatchTouchEvent', { type: 'touchStart', touchPoints: [{ x, y, id: 1 }] }) +await sleep(460) +console.log('after 460ms class:', await ev(`document.querySelector('[data-habit-id="2"]').className`)) +const r3 = JSON.parse(await ev(`JSON.stringify(document.querySelector('[data-habit-id="3"]').getBoundingClientRect())`)) +await S('Input.dispatchTouchEvent', { type: 'touchMove', touchPoints: [{ x: r3.left + r3.width / 2, y: r3.top + r3.height - 8, id: 1 }] }) +await sleep(80) +console.log('class of 3 after move:', await ev(`document.querySelector('[data-habit-id="3"]').className`)) +await S('Input.dispatchTouchEvent', { type: 'touchEnd', touchPoints: [] }) +await sleep(300) +console.log('events:', await ev('window.__ev.join(" | ")')) +ws.close() diff --git a/.local/cdp-reorder.mjs b/.local/cdp-reorder.mjs new file mode 100644 index 0000000..eb8ef21 --- /dev/null +++ b/.local/cdp-reorder.mjs @@ -0,0 +1,32 @@ +import WebSocket from 'ws' +const C = JSON.parse((await (await fetch('http://127.0.0.1:9223/json/version')).text())) +const ws = new WebSocket(C.webSocketDebuggerUrl, { perMessageDeflate: false }) +let id = 0; const pending = new Map() +const send = (m, p = {}, s) => new Promise((r) => { const i = ++id; pending.set(i, r); ws.send(JSON.stringify({ id: i, method: m, params: p, sessionId: s })) }) +ws.on('message', (d) => { const m = JSON.parse(d); if (m.id && pending.has(m.id)) { pending.get(m.id)(m.result); pending.delete(m.id) } }) +await new Promise((r) => ws.on('open', r)) +const { targetId } = await send('Target.createTarget', { url: 'about:blank' }) +const { sessionId } = await send('Target.attachToTarget', { targetId, flatten: true }) +const S = (m, p) => send(m, p, sessionId) +await S('Runtime.enable'); await S('Page.enable') +await S('Emulation.setDeviceMetricsOverride', { width: 400, height: 800, deviceScaleFactor: 2, mobile: true }) +await S('Emulation.setTouchEmulationEnabled', { enabled: true, maxTouchPoints: 5 }) +await S('Page.navigate', { url: 'http://127.0.0.1:5199/dragtest.html' }) +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) +const ev = async (e) => (await S('Runtime.evaluate', { expression: e, returnByValue: true })).result.value +for (let i = 0; i < 30; i++) { await sleep(1000); if (await ev(`!!document.querySelector('[data-task-id="101"]')`)) break } +const touch = (type, x, y) => S('Input.dispatchTouchEvent', { type, touchPoints: type === 'touchEnd' ? [] : [{ x, y, id: 1 }] }) + +// 用例1: 任务B(102) 把手拖到任务D(104) 下半 → 期望 REORDER 102 after 104 +const g = JSON.parse(await ev(`JSON.stringify(document.querySelector('[data-task-id="102"] .grip').getBoundingClientRect())`)) +await touch('touchStart', g.left + g.width / 2, g.top + g.height / 2) +await sleep(60) +const d = JSON.parse(await ev(`JSON.stringify(document.querySelector('[data-task-id="104"]').getBoundingClientRect())`)) +await touch('touchMove', d.left + d.width / 2, d.top + d.height - 10) +await sleep(120) +console.log('row104 class:', await ev(`document.querySelector('[data-task-id="104"]').className`)) +console.log('drag state:', await ev(`JSON.stringify({drag: !!document.querySelector('.task-row.dragging'), ghost: !!document.querySelector('.drag-ghost')})`)) +await touch('touchEnd') +await sleep(200) +console.log('events:', await ev('window.__ev.join(" | ")')) +ws.close() diff --git a/.local/cdp-scan.mjs b/.local/cdp-scan.mjs new file mode 100644 index 0000000..bf0ac2f --- /dev/null +++ b/.local/cdp-scan.mjs @@ -0,0 +1,40 @@ +import WebSocket from 'ws' +const C = JSON.parse((await (await fetch('http://127.0.0.1:9223/json/version')).text())) +const ws = new WebSocket(C.webSocketDebuggerUrl, { perMessageDeflate: false }) +let id = 0; const pending = new Map() +const send = (m, p = {}, s) => new Promise((r) => { const i = ++id; pending.set(i, r); ws.send(JSON.stringify({ id: i, method: m, params: p, sessionId: s })) }) +ws.on('message', (d) => { const m = JSON.parse(d); if (m.id && pending.has(m.id)) { pending.get(m.id)(m.result); pending.delete(m.id) } }) +await new Promise((r) => ws.on('open', r)) +const targets = await send('Target.getTargets') +const t = targets.targetInfos.find((x) => x.url.includes('dragtest')) +const { sessionId } = await send('Target.attachToTarget', { targetId: t.targetId, flatten: true }) +const S = (m, p) => send(m, p, sessionId) +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) +const ev = async (e) => (await S('Runtime.evaluate', { expression: e, returnByValue: true })).result.value +const touch = (type, x, y) => S('Input.dispatchTouchEvent', { type, touchPoints: type === 'touchEnd' ? [] : [{ x, y, id: 1 }] }) +const r = JSON.parse(await ev(`JSON.stringify(document.querySelector('[data-habit-id="2"] .grip').getBoundingClientRect())`)) +const x = r.left + r.width / 2, y = r.top + r.height / 2 +await touch('touchStart', x, y) +await sleep(60) +const t4 = JSON.parse(await ev(`JSON.stringify(document.querySelector('[data-habit-id="4"]').getBoundingClientRect())`)) +const fx = t4.left + t4.width / 2, fy = t4.top + t4.height / 2 +await touch('touchMove', fx, fy) +await sleep(150) +const scan = await ev(`(() => { + const out = [] + document.querySelectorAll('*').forEach((el) => { + const cs = getComputedStyle(el) + const b = el.getBoundingClientRect() + if (b.width === 0 || b.height === 0) return + const bg = cs.backgroundColor + // 找带蓝色背景的元素 + if (/rgb\\((22|2[0-9]|1[0-9]), ?1[0-9]{2}/.test(bg) || bg.includes('22, 119, 255') || bg.includes('0, 149, 255')) { + out.push({ tag: el.tagName, cls: el.className && String(el.className).slice(0, 40), bg, rect: [Math.round(b.left), Math.round(b.top), Math.round(b.width), Math.round(b.height)], text: (el.textContent || '').slice(0, 12) }) + } + }) + return JSON.stringify(out) +})()`) +console.log('blue elements:', scan) +console.log('ghost children:', await ev(`(() => { const g = document.querySelector('.drag-ghost'); return g ? g.children.length + ' kids, inner w ' + g.getBoundingClientRect().width : 'none' })()`)) +await touch('touchEnd') +ws.close() diff --git a/.local/cdp-shot.mjs b/.local/cdp-shot.mjs new file mode 100644 index 0000000..31b02c7 --- /dev/null +++ b/.local/cdp-shot.mjs @@ -0,0 +1,35 @@ +import WebSocket from 'ws' +import fs from 'node:fs' +const C = JSON.parse((await (await fetch('http://127.0.0.1:9223/json/version')).text())) +const ws = new WebSocket(C.webSocketDebuggerUrl, { perMessageDeflate: false }) +let id = 0; const pending = new Map() +const send = (m, p = {}, s) => new Promise((r) => { const i = ++id; pending.set(i, r); ws.send(JSON.stringify({ id: i, method: m, params: p, sessionId: s })) }) +ws.on('message', (d) => { const m = JSON.parse(d); if (m.id && pending.has(m.id)) { pending.get(m.id)(m.result); pending.delete(m.id) } }) +await new Promise((r) => ws.on('open', r)) +const { targetId } = await send('Target.createTarget', { url: 'about:blank' }) +const { sessionId } = await send('Target.attachToTarget', { targetId, flatten: true }) +const S = (m, p) => send(m, p, sessionId) +await S('Runtime.enable'); await S('Page.enable') +await S('Emulation.setDeviceMetricsOverride', { width: 400, height: 800, deviceScaleFactor: 2, mobile: true }) +await S('Emulation.setTouchEmulationEnabled', { enabled: true, maxTouchPoints: 5 }) +await S('Page.navigate', { url: 'http://127.0.0.1:5199/dragtest.html' }) +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) +const ev = async (e) => (await S('Runtime.evaluate', { expression: e, returnByValue: true })).result.value +for (let i = 0; i < 30; i++) { await sleep(1000); if (await ev(`!!document.querySelector('[data-habit-id="1"]')`)) break } +const touch = (type, x, y) => S('Input.dispatchTouchEvent', { type, touchPoints: type === 'touchEnd' ? [] : [{ x, y, id: 1 }] }) +const r = JSON.parse(await ev(`JSON.stringify(document.querySelector('[data-habit-id="2"] .grip').getBoundingClientRect())`)) +const x = r.left + r.width / 2, y = r.top + r.height / 2 +await touch('touchStart', x, y) +await sleep(60) +const t = JSON.parse(await ev(`JSON.stringify(document.querySelector('[data-habit-id="4"]').getBoundingClientRect())`)) +await touch('touchMove', t.x ?? t.left + t.width / 2, t.top + t.height / 2) +await sleep(120) +// 拖动中截图 +const shot = await S('Page.captureScreenshot', { format: 'png' }) +fs.writeFileSync(process.env.USERPROFILE + '\\AppData\\Local\\Temp\\drag-mid.png', Buffer.from(shot.data, 'base64')) +console.log('shot saved; ghost exists:', await ev(`!!document.querySelector('.drag-ghost')`)) +console.log('ghost html head:', (await ev(`document.querySelector('.drag-ghost') ? document.querySelector('.drag-ghost').outerHTML.slice(0, 200) : 'NONE'`))) +await touch('touchEnd') +await sleep(200) +console.log('events:', await ev('window.__ev.join(" | ")')) +ws.close() diff --git a/.local/cdp-task-grip.mjs b/.local/cdp-task-grip.mjs new file mode 100644 index 0000000..8f516c2 --- /dev/null +++ b/.local/cdp-task-grip.mjs @@ -0,0 +1,36 @@ +import WebSocket from 'ws' +const C = JSON.parse((await (await fetch('http://127.0.0.1:9223/json/version')).text())) +const ws = new WebSocket(C.webSocketDebuggerUrl, { perMessageDeflate: false }) +let id = 0; const pending = new Map() +const send = (m, p = {}, s) => new Promise((r) => { const i = ++id; pending.set(i, r); ws.send(JSON.stringify({ id: i, method: m, params: p, sessionId: s })) }) +ws.on('message', (d) => { const m = JSON.parse(d); if (m.id && pending.has(m.id)) { pending.get(m.id)(m.result); pending.delete(m.id) } }) +await new Promise((r) => ws.on('open', r)) +const { targetId } = await send('Target.createTarget', { url: 'about:blank' }) +const { sessionId } = await send('Target.attachToTarget', { targetId, flatten: true }) +const S = (m, p) => send(m, p, sessionId) +await S('Runtime.enable'); await S('Page.enable') +await S('Emulation.setDeviceMetricsOverride', { width: 400, height: 800, deviceScaleFactor: 2, mobile: true }) +await S('Emulation.setTouchEmulationEnabled', { enabled: true, maxTouchPoints: 5 }) +await S('Page.navigate', { url: 'http://127.0.0.1:5199/dragtest.html' }) +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) +const ev = async (e) => (await S('Runtime.evaluate', { expression: e, returnByValue: true })).result.value +for (let i = 0; i < 30; i++) { await sleep(1000); if (await ev(`!!document.querySelector('[data-task-id="3"]')`)) break } +await sleep(1000) // 等延迟数据加载完 +const touch = (type, x, y) => S('Input.dispatchTouchEvent', { type, touchPoints: type === 'touchEnd' ? [] : [{ x, y, id: 1 }] }) + +// 用例: 按住任务3的把手 → 水平拖 +200px (带 +30 纵向漂移, 复现真机抖动) → 松手 +const r = JSON.parse(await ev(`JSON.stringify(document.querySelector('[data-task-id="3"] .grip').getBoundingClientRect())`)) +let x = r.left + r.width / 2, y = r.top + r.height / 2 +console.log('grip rect', JSON.stringify(r)) +await touch('touchStart', x, y) +await sleep(60) +for (const [dx, dy] of [[25, 4], [60, 10], [110, 18], [160, 26], [200, 30]]) { + await touch('touchMove', x + dx, y + dy) + await sleep(50) +} +console.log('row class after moves:', await ev(`document.querySelector('[data-task-id="3"]').className`)) +console.log('meta:', await ev(`document.querySelector('[data-task-id="3"] .task-meta').textContent`)) +await touch('touchEnd') +await sleep(300) +console.log('events:', await ev('window.__ev.join(" | ")')) +ws.close() diff --git a/.local/cdp-verify.mjs b/.local/cdp-verify.mjs new file mode 100644 index 0000000..18699ce --- /dev/null +++ b/.local/cdp-verify.mjs @@ -0,0 +1,30 @@ +import WebSocket from 'ws' +import fs from 'node:fs' +const C = JSON.parse((await (await fetch('http://127.0.0.1:9223/json/version')).text())) +const ws = new WebSocket(C.webSocketDebuggerUrl, { perMessageDeflate: false }) +let id = 0; const pending = new Map() +const send = (m, p = {}, s) => new Promise((r) => { const i = ++id; pending.set(i, r); ws.send(JSON.stringify({ id: i, method: m, params: p, sessionId: s })) }) +ws.on('message', (d) => { const m = JSON.parse(d); if (m.id && pending.has(m.id)) { pending.get(m.id)(m.result); pending.delete(m.id) } }) +await new Promise((r) => ws.on('open', r)) +const targets = await send('Target.getTargets') +const t = targets.targetInfos.find((x) => x.url.includes('dragtest')) +const { sessionId } = await send('Target.attachToTarget', { targetId: t.targetId, flatten: true }) +const S = (m, p) => send(m, p, sessionId) +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) +const ev = async (e) => (await S('Runtime.evaluate', { expression: e, returnByValue: true })).result.value +await S('Page.reload') +await sleep(2500) +const touch = (type, x, y) => S('Input.dispatchTouchEvent', { type, touchPoints: type === 'touchEnd' ? [] : [{ x, y, id: 1 }] }) +const r = JSON.parse(await ev(`JSON.stringify(document.querySelector('[data-habit-id="2"] .grip').getBoundingClientRect())`)) +const x = r.left + r.width / 2, y = r.top + r.height / 2 +await touch('touchStart', x, y) +await sleep(60) +const t4 = JSON.parse(await ev(`JSON.stringify(document.querySelector('[data-habit-id="4"]').getBoundingClientRect())`)) +await touch('touchMove', t4.left + t4.width / 2, t4.top + t4.height / 2) +await sleep(150) +console.log('ghost bg:', await ev(`getComputedStyle(document.querySelector('.drag-ghost')).backgroundColor`)) +console.log('ghost rect:', await ev(`JSON.stringify(document.querySelector('.drag-ghost').getBoundingClientRect())`)) +const shot = await S('Page.captureScreenshot', { format: 'png' }) +fs.writeFileSync(process.env.USERPROFILE + '\\AppData\\Local\\Temp\\drag-fixed.png', Buffer.from(shot.data, 'base64')) +await touch('touchEnd') +ws.close() diff --git a/.local/msg.txt b/.local/msg.txt new file mode 100644 index 0000000..3833c95 --- /dev/null +++ b/.local/msg.txt @@ -0,0 +1,8 @@ +fix(V0.2): 拖动浮层残留蓝色背景 —— 类名与遗留 .drag-ghost 规则冲突 + +globals.css 里有一条无人使用的旧 .drag-ghost 规则 (蓝色气泡, +max-width 180px, 看板早期方案遗留), 排在新增规则之后同特异性胜出, +克隆卡片容器被套上蓝色背景并被压缩到 180px 宽, 即真机上手下面的 +蓝色 div。删除遗留规则; 新规则显式清零背景/内边距并禁用 +tap-highlight/user-select (克隆脱离 .mv-view/.task-content 祖先后 +需自带防选中)。CDP 实测拖动中容器背景已透明、宽度正常。 diff --git a/.local/re-test.mjs b/.local/re-test.mjs new file mode 100644 index 0000000..95e97c5 --- /dev/null +++ b/.local/re-test.mjs @@ -0,0 +1,4 @@ +const s = '问题,;、!' +console.log(JSON.stringify(s.replace(/[\s\p{P}]+$/u, ''))) +console.log(JSON.stringify('正常标题'.replace(/[\s\p{P}]+$/u, ''))) +console.log(JSON.stringify('a-b_c'.replace(/[\s\p{P}]+$/u, ''))) diff --git a/.local/tmp/system-commandline-sentinel-files/dotnet-suggest-registration-git-credential-manager, Version=2.9.0.0, Culture=neutral, PublicKeyToken=null b/.local/tmp/system-commandline-sentinel-files/dotnet-suggest-registration-git-credential-manager, Version=2.9.0.0, Culture=neutral, PublicKeyToken=null new file mode 100644 index 0000000..4db2c93 --- /dev/null +++ b/.local/tmp/system-commandline-sentinel-files/dotnet-suggest-registration-git-credential-manager, Version=2.9.0.0, Culture=neutral, PublicKeyToken=null @@ -0,0 +1,5 @@ +Exception during registration: +System.ComponentModel.Win32Exception (0x80004005): 系统找不到指定的文件。 + 在 System.Diagnostics.Process.StartWithCreateProcess(ProcessStartInfo startInfo) + 在 System.CommandLine.Invocation.Process.StartProcess(String command, String args, String workingDir, Action`1 stdOut, Action`1 stdErr, ValueTuple`2[] environmentVariables) + 在 System.CommandLine.Builder.CommandLineBuilderExtensions.<>c.<b__5_1>d.MoveNext() \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0137421 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,31 @@ +# CHANGELOG + +版本号规则见 [README.md](README.md)「版本管理」。前端与外壳版本号**互相独立**、独立发版, +分三部分记录;PATCH 仅在明确提出时自增, 否则沿用, 每次变更在对应部分追加一行。 + +--- + +## 程序版本 (Web / OTA) + +**当前值: 0.2.0** (管理点: `package.json` 的 `version`; OTA 包 = 同值 + `-ota`) + +| 日期 | 版本 | commit | 要点 | +| --- | --- | --- | --- | +| 2026-10-25 | 0.2.0 | — | V0.2 基线。前端与外壳版本解绑; build 标识统一 `YYYYMMDDHHmmss` (北京时间); PATCH 规则确立 (仅明确提出时自增) | +| 2026-10-25 | 0.2.1 | 93b7c7b | 主要解决离线编辑与不同终端数据合并问题 | + +## Android 外壳版本 + +**当前值: 0.2.0** (管理点: `src-tauri/tauri.conf.json` 的 `version`, 须与 Cargo.toml/Cargo.lock 组内一致; 产物 = APK versionName) + +| 日期 | 版本 | commit | 要点 | +| --- | --- | --- | --- | +| 2026-10-25 | 0.2.0 | — | V0.2 基线。外壳与前端版本解绑, 独立维护 (android-* 分支出包) | + +## Windows 外壳版本 + +**当前值: —** (尚未建立; 建立后管理点同为 `src-tauri/tauri.conf.json` + Cargo 三件套, 走独立分支/打包工作流) + +| 日期 | 版本 | commit | 要点 | +| --- | --- | --- | --- | +| — | — | — | 暂无 | diff --git a/Dockerfile b/Dockerfile index 7b4405c..07c9086 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,73 +1,85 @@ -# =============================================================== -# iBoard 一体化镜像: 静态前端 (dist/) + Express API (server/) 同进程 -# 构建: docker build --build-arg DATABASE_URL=... -t iboard:latest . -# 可选 --build-arg VITE_API_BASE=https://api.example.com 让前端走绝对地址 -# 运行: docker run -d --name iboard -p 3001:3001 -e DATABASE_URL=... iboard:latest -# =============================================================== -FROM node:22-alpine AS builder - -WORKDIR /app - -# 国内构建时可换源 -RUN npm config set registry https://registry.npmmirror.com - -# 先拷贝 package 文件, 利用 Docker 缓存 -COPY package*.json ./ -COPY prisma ./prisma/ -COPY prisma.config.ts ./ -COPY generated ./generated/ - -# 装全量依赖 (dev 也装, 后面 build 要用) -RUN npm install - -# 拷贝源码 -COPY . . - -# 构建前端 (输出到 dist/) 与 Prisma 客户端 -RUN npx prisma generate -# build 时把 VITE_API_BASE 写进 bundle, 默认空 = 走相对路径 /api (同源 Express) -ARG VITE_API_BASE= -ENV VITE_API_BASE=$VITE_API_BASE -RUN npm run build - -# 运行时阶段 -FROM node:22-alpine - -WORKDIR /app - -# 阿里源加速 -RUN npm config set registry https://registry.npmmirror.com - -# 只装运行时依赖 -COPY package*.json ./ -RUN npm install --omit=dev - -# 拷贝构建产物 -COPY --from=builder /app/dist ./dist -COPY --from=builder /app/server ./server -COPY --from=builder /app/generated ./generated -COPY --from=builder /app/prisma ./prisma -COPY --from=builder /app/prisma.config.ts ./prisma.config.ts -COPY --from=builder /app/scripts ./scripts -COPY --from=builder /app/content ./content -COPY --from=builder /app/public ./public - -# OTA 升级包: CI (docker-build.yml) 生成的 ota-package/, 随镜像发布 -# 本地构建时只有占位文件 (ota-package/README.md), server 找不到 latest.json 则 OTA 返回"无更新" -COPY ota-package ./ota-package -ENV OTA_PACKAGE_DIR=/app/ota-package -# OTA bundle 下载用的绝对 base (manifest.url 必须是绝对 URL, 插件不拼 base, 相对路径会导致下载 0% 失败) -# 生产为 http 明文 (无 https), 客户端 tauri.conf.json 的 hotswap.require_https 已设为 false 放行 http -ENV OTA_PUBLIC_BASE=http://bh.vps.honor3.com:3001 - -# 健康检查 -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD wget -qO- http://127.0.0.1:3001/api/health || exit 1 - -EXPOSE 3001 - -ENV NODE_ENV=production -ENV PORT=3001 - -# Node 22 默认未开 strip-types, 显式开启以便加载 generated/prisma/*.ts +# syntax=docker/dockerfile:1 +# =============================================================== +# iBoard 一体化镜像: 静态前端 (dist/) + Express API (server/) 同进程 +# 构建: docker build --build-arg DATABASE_URL=... -t iboard:latest . +# 可选 --build-arg VITE_API_BASE=https://api.example.com 让前端走绝对地址 +# CI 传入 --build-arg PREBUILT_DIST=true 复用 CI 已构建的 dist/ (跳过镜像内前端构建) +# 运行: docker run -d --name iboard -p 3001:3001 -e DATABASE_URL=... iboard:latest +# 需要 BuildKit (cache mount 语法; Docker 23+ 默认开启) +# =============================================================== +FROM node:22-alpine AS builder + +WORKDIR /app + +# 国内构建时可换源 +RUN npm config set registry https://registry.npmmirror.com + +# 先拷贝 package 文件, 利用 Docker 缓存 +COPY package*.json ./ +COPY prisma ./prisma/ +COPY prisma.config.ts ./ +COPY generated ./generated/ + +# 装全量依赖 (dev 也装, 后面 prisma generate / 本地构建前端要用) +# cache mount 复用 npm 下载缓存, 不进镜像层 +RUN --mount=type=cache,target=/root/.npm npm ci + +# 拷贝源码 +COPY . . + +# 构建 Prisma 客户端 +RUN --mount=type=cache,target=/root/.npm npx prisma generate + +# 构建前端 (输出到 dist/) +# PREBUILT_DIST=true: CI 已在镜像外构建过 dist/ (供 OTA 打包), 直接复用, 跳过镜像内构建 +ARG PREBUILT_DIST=false +# build 时把 VITE_API_BASE 写进 bundle, 默认空 = 走相对路径 /api (同源 Express) +ARG VITE_API_BASE= +ENV VITE_API_BASE=$VITE_API_BASE +RUN if [ "$PREBUILT_DIST" = "true" ]; then \ + test -f dist/index.html || { echo "PREBUILT_DIST=true 但 dist/ 缺失"; exit 1; }; \ + else \ + npm run build; \ + fi + +# 运行时阶段 +FROM node:22-alpine + +WORKDIR /app + +# 阿里源加速 +RUN npm config set registry https://registry.npmmirror.com + +# 只装运行时依赖 (better-sqlite3 是 @powersync/node 的 devOptional peer, 需同时 omit optional 才能跳过) +COPY package*.json ./ +RUN --mount=type=cache,target=/root/.npm npm ci --omit=dev --omit=optional + +# 拷贝构建产物 +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/server ./server +COPY --from=builder /app/generated ./generated +COPY --from=builder /app/prisma ./prisma +COPY --from=builder /app/prisma.config.ts ./prisma.config.ts +COPY --from=builder /app/scripts ./scripts +COPY --from=builder /app/content ./content +COPY --from=builder /app/public ./public + +# OTA 升级包: CI (docker-build.yml) 生成的 ota-package/, 随镜像发布 +# 本地构建时只有占位文件 (ota-package/README.md), server 找不到 latest.json 则 OTA 返回"无更新" +COPY ota-package ./ota-package +ENV OTA_PACKAGE_DIR=/app/ota-package +# OTA bundle 下载用的绝对 base (manifest.url 必须是绝对 URL, 插件不拼 base, 相对路径会导致下载 0% 失败) +# 生产为 http 明文 (无 https), 客户端 tauri.conf.json 的 hotswap.require_https 已设为 false 放行 http +ENV OTA_PUBLIC_BASE=https://iboard.honor3.com + +# 健康检查 (端口跟随 PORT 环境变量, 0.2.1 部署为 13001) +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget -qO- http://127.0.0.1:${PORT:-3001}/api/health || exit 1 + +EXPOSE 3001 + +ENV NODE_ENV=production +ENV PORT=3001 + +# Node 22 默认未开 strip-types, 显式开启以便加载 generated/prisma/*.ts CMD ["node", "--experimental-strip-types", "--no-warnings", "server/index.js"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ec97cee --- /dev/null +++ b/README.md @@ -0,0 +1,276 @@ +# iBoard + +跨平台个人看板 (Tauri + React + Express + MariaDB)。用于收集和整理经济数据,但是以策略为主体重新组织。 + +- 首页主要是放经济文章 +- 数据主要是放各种经济数据和图表 +- 策略是放按照经济理论组织的相关数据和图表的对比 + +## 首页 + +首页显示新闻模块和报警模块的数据,按照时间最新优先,无限滚动,直到数据库中没有数据为止。 + +### 新闻模块 + +增加一个新闻模块,数据来源是每日夜间 3 点,通过妙想 MCP 获取国内外最新经济政治新闻,按影响大小排序 10 条。存入数据库。 + +### 报警模块 + +World Bank 中的数据只用来对比妙想 MCP 拉取的数据正确性。当同一时间的同一项数据不同时,记录一个报警。 + +## 监控 + +监控分为服务器监控和服务监控两部分。服务器监控对接 komari,地址: + +--- + +## 分支规则 + +### 分支类型与职责 + +| 分支 | 职责 | 部署 | 合并方向 | +| --- | --- | --- | --- | +| `master` | **主分支**(生产分支) | ✅ 唯一部署出口,push 即发布生产 | — | +| `dev-*` | **开发集成分支**,如 `dev-0.2` | ❌ 不部署(只跑 CI 校验) | 合到 `master` 后由 master 部署 | +| `tmp-*` | **临时分支**,针对单个问题的修改 | ❌ 不部署 | 合到对应 `dev-*` 后删除 | +| `android-*` | **Android 外壳构建分支** | ❌ 不部署,只触发 APK 构建 | 构建前须与 `master` 同步 | + +> **关于 `main`**:团队约定里的「main 主分支」在本仓库落地为 **`master`**(仓库默认分支即 `master`,不另建 `main`)。 +> 下文所有「main 主分支」均指 `master`。 + +### 流程 + +``` +tmp-xxx ──合并──▶ dev-0.2 ──合并──▶ master ──push──▶ 自动部署生产 + │ │ + │ └──push──▶ 只跑 CI(版本一致性校验),不部署 + │ + └── 问题解决后删除 + +android-1.0 ── 从 master 同步代码后 push ──▶ 只触发 APK 构建 / 发布到 Packages +``` + +1. **日常开发**:从 `dev-*` 拉 `tmp-*`,改完合回 `dev-*`,`tmp-*` 用完即删。 +2. **上线**:`dev-*` 验收通过后合并到 `master`,**推送 `master` 才部署生产**。 +3. **打 Android 包**:`android-*` 与 `master` 同步(`git merge master` 或 `git reset --hard origin/master`)后推送, + 触发 [android-build.yml](.gitea/workflows/android-build.yml),产物发布到 Gitea Generic Packages。 + +### CI 触发范围 + +| 工作流 | 触发分支 | 动作 | +| --- | --- | --- | +| [docker-build.yml](.gitea/workflows/docker-build.yml) | **仅 `master`** | 构建前端 + 生成签名 OTA 包 + 构建镜像 + 迁移数据库 + 部署生产 | +| [android-build.yml](.gitea/workflows/android-build.yml) | `android-*` | 构建并签名 release APK,上传到 Packages | +| [ota-build.yml](.gitea/workflows/ota-build.yml) | 仅手动 `workflow_dispatch` | 单独构建/签发 OTA 包 | +| [sync-registry.yml](.gitea/workflows/sync-registry.yml) | 见文件 | 同步注册中心 | + +> ⚠️ **重要变更(2026-10)**:`dev-*` 分支**已取消** push 自动部署。 +> 此前 `dev-*` 与 `master` 共用同一套生产环境,推送开发分支等于直接发布,容易把半成品带上线。 +> 现在开发分支的提交只有合并进 `master` 并推送后才会部署。 + +### 日常操作规则(工作目录约定) + +- 主工作目录(`iboard/`)**默认停留在 `dev-0.2`**,所有修改都在 `dev-0.2` 上提交;`dev-0.2` 可随时 push(只跑 CI 校验,不部署)。 +- 独立功能开发使用**独立分支 + 独立工作目录**(如离线编辑在 `iboard-dev-0.2.1/` 的 `tmp-offline-edit` 分支),不占用 `dev-0.2`。 +- **发布流程(仅在明确需要发布时执行,结束后立刻回到 `dev-0.2`)**: + + ```bash + git switch master + git merge --ff-only dev-0.2 + git push origin master # push 即部署生产 + git switch dev-0.2 + ``` + +--- + +## 版本管理 + +### 版本号构成与口径(2026-10-25 决议) + +``` +V MAJOR.MINOR.PATCH 例: V0.2.0 + │ └── PATCH 小版本号: 仅在明确提出时自增, 否则沿用 (记录见 CHANGELOG.md) + └── MINOR 大版本迭代, 对应一个开发分支 (V0.2 ↔ dev-0.2) +``` + +**前端与外壳解绑,独立维护、独立发版**(2026-10-25 决议): + +| 组 | 版本 | 管理点 | 产物 | 发版节奏 | +| --- | --- | --- | --- | --- | +| **前端** (Web/OTA) | `0.2.0` | `package.json` 的 `version`(唯一手动管理点,只写裸版本号,不允许带 `-ota`) | 前端运行时 `APP_VERSION` = `0.2.0`;OTA 包 `version` = `0.2.0-ota` | 随 master push 高频发版 | +| **外壳** | `0.2.0` | `src-tauri/tauri.conf.json` 的 `version`(须与 `Cargo.toml`/`Cargo.lock` 组内一致) | APK versionName、桌面安装包版本;后续 Windows 外壳同理 | 独立分支(如 `android-*`)低频发版 | + +- 两组版本号**不要求相等**,CI(`scripts/ci/check-version.mjs`)只校验组内一致 + 前端版本与 `dev-.` 分支前缀对应。 +- 分支口径:**dev-0.2 开发,发布一律走 master**。 + +### build 标识 + +- 格式统一为 **`YYYYMMDDHHmmss`(北京时间)**: + - 前端 `BUILD_ID`:vite 编译时生成(`vite.config.js#makeBuildId`),无需手动维护; + - OTA 包 `build_id`:打包时按同一格式生成(`scripts/build-ota.mjs`)。 +- OTA 包的 `sequence`(Unix 毫秒,单调递增)保留,仅用于判新与热插拔协议,不再用于展示。 + +### PATCH 小版本号 + +- 仅在**明确提出**时自增(改 `package.json` version 第三位),否则一直沿用。 +- 每次自增在 [CHANGELOG.md](CHANGELOG.md) 追加一行(patch 号 / 日期 / commit / 要点)。 + +### 改版本号时要同步的位置 + +| 组 | 文件 | 字段 | 说明 | +| --- | --- | --- | --- | +| 前端 | `package.json` | `version` | **前端唯一源**,`src/version.js` 与 OTA 打包脚本均读取此值 | +| 外壳 | `src-tauri/tauri.conf.json` | `version` | APK 的 versionName、桌面安装包版本 | +| 外壳 | `src-tauri/Cargo.toml` | `[package].version` | Rust 包版本,须与 tauri.conf.json 一致 | +| 外壳 | `src-tauri/Cargo.lock` | `name = "iboard"` 条目 | 随 Cargo.toml 一起改 | + +### APK 版本号是怎么来的 + +`tauri android init` 会按 `src-tauri/tauri.conf.json` 的 `version` 生成 +`src-tauri/gen/android/app/tauri.properties`(该目录被 gitignore,CI 每次 `android init` 重新生成): + +``` +tauri.android.versionName = 0.2.0 # 人类可见版本,与 tauri.conf.json 一致 +tauri.android.versionCode = 2000 # 整数,升级判定用 = major*1000000 + minor*1000 + patch +``` + +注意: +- `versionCode` 必须**单调递增**才能覆盖安装,所以外壳版本号只能往上走(`0.2.0` → 2000 > `0.1.0` → 1000)。 +- 本地如果 APK 装完显示旧版本号,是 `src-tauri/gen/android` 缓存没刷新, + 删掉该目录重新 `npx tauri android init` 即可。 + +### 其它 + +- `src/version.js` 从 `package.json` 取值,前端运行时与 OTA 检查接口展示的即此版本,不要另写死版本号。 +- **`min_binary_version` 与这里的版本号是两件事**:它是 OTA 包要求的最低原生二进制版本 + (CI 里由 `OTA_MIN_BINARY` 显式指定,当前 `0.1.0`)。前端版本升到 0.2.0 时它不应跟着抬, + 否则装了 0.1.0 APK 的设备会直接收不到热更。 +- `ota-package/latest.json` 为运行时产物,不纳入版本管理。 + +### "我的"页版本展示口径 + +| 行 | 展示 | +| --- | --- | +| 当前版本 | `v0.2.0 · build 20261025204300`(前端 APP_VERSION + BUILD_ID 原始格式) | +| 运行资源 | OTA 覆盖包版本 / 内置 | +| 新版本 | `v0.2.0-ota · build 20261025210000`(OTA 包 build_id) | +| 发布说明 | 触发构建的 commit message | +| 提交 | commit sha 前 10 位 | +| 页面底部 | `外壳 v0.2.0`(Tauri API 读取;Web 端显示 `—`) | + +--- + +## 前端 OTA 升级 + +只升级前端资源 (`dist/`),**不触碰** Tauri 框架/原生层,不重打 APK。 +底层使用 [`tauri-plugin-hotswap`](https://github.com/denniskribl/tauri-plugin-hotswap) +(签名/回滚/资源替换全部由插件处理)。 + +### 工作流 + +``` +开发者 服务端 +npm run build ──┐ +scripts/build-ota.mjs │ 生成 iboard-ota.tar.gz + minisign 签名 + │ 输出 latest.json (sequence/version/...) + ▼ + ota-package/ ── 部署到服务器 ──▶ /srv/iboard/ota-package + │ + ▼ + GET /api/ota/check/ +客户端 (Tauri) │ +启动 ─▶ notifyReady() ▼ +我页 ─▶ checkUpdate() ◀──── 200 + manifest (含 url/signature) ─────┘ + applyUpdate() ─▶ 下载 tar.gz ─▶ minisign 校验 ─▶ 解压到 AppData/ota-dist + location.reload() ─▶ 后续资源从 tauri://localhost 优先命中覆盖包 +``` + +### 发布说明与提交号(2026-10-25 起) + +- CI(docker-build.yml)把**触发构建的 commit message**(标题+正文,去空行,截断 400 字)作为 `notes` 写入 latest.json; +- commit sha 经 `--commit` 参数单独写入 `commit` 字段; +- "我的"页:发布说明展示 commit message,「提交」行展示短 sha。 + +### 首次部署 + +1. **生成签名密钥** (一次性): + ```bash + # 安装 minisign: Windows (scoop) / macOS (brew) / Linux (apt) + node scripts/ota-genkey.mjs + # 产物: ota-package/hotswap.{key, pub} + ``` +2. **把公钥写入 `src-tauri/tauri.conf.json`** 的 `plugins.hotswap.pubkey` 字段(脚本会打印)。 +3. **配置 endpoint** (`src-tauri/tauri.conf.json`): + ```json + "hotswap": { + "endpoint": "https://your-server/api/ota/check/{{current_sequence}}", + "pubkey": "RWR...", + "require_https": true + } + ``` +4. **服务端环境变量** `OTA_PACKAGE_DIR` 指向 `ota-package/` 所在目录。 + +### 发布新版前端 + +```bash +npm run build # 生成 dist/ +node scripts/build-ota.mjs # 打成 tar.gz + 签名 + latest.json +# sequence 默认 = Date.now(), 也可 --sequence 43 手动指定 +# 也可加 --notes "修复登录按钮" --commit --min-binary 0.1.0 +``` + +然后把 `ota-package/` 下两个文件 (`iboard-ota.tar.gz` + `latest.json`) 同步到 +服务端 `OTA_PACKAGE_DIR` 指向的目录。sequence 单调递增即可。 + +### 客户端使用 + +打开 App 的"我"页,点 [检查更新] → [立即升级] 即可。失败会自动回滚到内置 dist。 + +> **注意:** Tauri 2 的 dev 模式 (`tauri dev`) 会代理到 Vite dev server,**OTA 不生效**。 +> OTA 仅在 release/打包构建中可用,测试请用 `tauri build` 产物。 + +### 客户端判新口径(V0.2) + +"我"页 OTA 组内有一个 **开发版本** 开关(默认关闭),切换"怎么算新版本": + +| 开关 | 判新依据 | 适用 | +| --- | --- | --- | +| 关(默认) | **版本号**逐字段比大小(`0.2.0-ota` > `0.1.0` 才算新) | 生产。旧分支被重建(sequence 变大但版本号更小)时不会把设备拉回旧版 | +| 开 | **build sequence**(服务端 sequence 比本地大就算新) | 开发联调。每次构建都能推到设备 | + +- 开关值持久化在设备端 `localStorage.iboard_ota_dev_mode`(`'1'`/`'0'`),默认 `'0'`。 +- 版本比较在 `src/utils/version.js`(纯函数,`src/api/ota.js` 统一封装两种模式)。 +- **`min_binary_version` 与应用版本是两件事**:前者是前端 bundle 要求的最低原生二进制版本, + 当前 APK 原生层仍是 `0.1.0`,所以 CI 里固定传 `--min-binary 0.1.0`(见 `docker-build.yml` 的 + `OTA_MIN_BINARY`)。只改前端版本号时**不要**跟着抬这个值,否则旧 APK 会收不到更新。 + +### Web 端 + +Web 浏览器访问 (同源) 时,服务端返回简化版 manifest,仅显示"有新版本请刷新"。 +Web 端不会自动下载包,也不会替换前端代码。 + +### CI 自动签名 (Gitea Actions) + +.gitea/workflows/ota-build.yml 是手动触发的 OTA 打包工作流(每次发版前点一次)。 + +**配置 secret** (Gitea 仓库 -> Settings -> Actions -> Secrets): +- MINISIGN_PRIVATE_KEY: 完整 .key 文件内容,**整段**(含首行 `untrusted comment: minisign encrypted secret key` 和 base64 行),整段作为多行 secret 粘贴即可。 + +**使用流程**: +1. 在 Gitea Actions 页面选 OTA Frontend Build & Sign -> Run workflow +2. 填 sequence(可选,不填用时间戳)和 notes(可选) +3. CI 跑完会在 artifact 里看到 ota-package/iboard-ota.tar.gz + latest.json + iboard-ota.tar.gz.minisig +4. 下载 artifact,scp 到服务器 OTA_PACKAGE_DIR 目录 +5. 客户端"我"页 [检查更新] 即可触发升级 + +**注意**: +- CI 用的私钥必须**与** `tauri.conf.json` 里 plugins.hotswap.pubkey **配对**;换密钥时需要同时更新 CI secret 和 conf。 +- 不要把私钥 commit 到 git,整个 ota-package/hotswap.key 已被 .gitignore 排除。 + +### 数据库升级约定 + +- 唯一入口是 `prisma/migrations/` 下的迁移文件,由 `npx prisma migrate deploy` 在**启新容器之前**执行 + (幂等,无新迁移时为 no-op);迁移失败会中断 workflow,不会用新镜像顶掉仍在运行的旧容器。 +- 由于只有 `master` 会部署,**迁移真正作用到生产库的唯一时机就是 master 发布**; + 开发分支上的迁移不会被自动应用,请在合并到 `master` 前确认可回滚。 +- 本地改了 `prisma/schema.prisma` 后:写迁移 SQL → `npx prisma migrate deploy` 验证 → 提交迁移目录。 diff --git a/deploy/0.2.1/.env.example b/deploy/0.2.1/.env.example new file mode 100644 index 0000000..11a8879 --- /dev/null +++ b/deploy/0.2.1/.env.example @@ -0,0 +1,11 @@ +# 复制为 .env (服务器上 chmod 600, 已 gitignore)。 +# POWERSYNC_JWT_KEY_B64 = base64url(POWERSYNC_JWT_SECRET 的 UTF-8 字节) +# 生成: printf %s "$POWERSYNC_JWT_SECRET" | openssl base64 -A | tr '+/' '-_' | tr -d '=' + +PG_PASSWORD=change-me +PS_STORAGE_PASSWORD=change-me +JWT_SECRET_021=change-me +POWERSYNC_JWT_SECRET=change-me +POWERSYNC_JWT_KEY_B64=change-me +POWERSYNC_URL=https://bh.vps.honor3.com:13443 +OTA_PUBLIC_BASE=http://bh.vps.honor3.com:13001 diff --git a/deploy/0.2.1/README.md b/deploy/0.2.1/README.md new file mode 100644 index 0000000..aad14ac --- /dev/null +++ b/deploy/0.2.1/README.md @@ -0,0 +1,61 @@ +# 0.2.1 独立验证部署 (baihu /app/iboard-0.2.1) + +与现网 `/app/iboard`(3001/宿主 MySQL) 完全隔离:本栈使用独立 PG 容器、端口 13443/13001/13002,不写原库。 + +**浏览器入口: `https://bh.vps.honor3.com:13443`**(自签证书,首次访问需在浏览器"高级→继续访问"接受一次;PowerSync 网页端必须运行在安全上下文,http://13001 入口仅供接口调试,任务模块会报 Navigator locks 错误)。 +正式启用需替换受信证书:把 `bh.pem/bh.key` 换成受信证书后 `docker compose restart tls-front`(例如 Let's Encrypt DNS-01,80/443 已被 gateway 占用无法 HTTP-01)。 + +## 栈 + +| 服务 | 说明 | +| --- | --- | +| `pg` | postgres:16-alpine,`wal_level=logical`(供 PowerSync 逻辑复制),数据在 `./data/pg`,**不对外暴露端口** | +| `app` | 本仓库 Dockerfile 构建,13001 对外;启动时先 `prisma migrate deploy`;`DISABLE_CRON=1` 关闭定时任务 | +| `powersync` | journeyapps/powersync-service,13002 对外;源库=pg,bucket storage=同实例 PG 的 powersync schema | + +## 首次部署 + +```bash +# 服务器上 (源码已上传到 /app/iboard-0.2.1) +cd /app/iboard-0.2.1/deploy/0.2.1 + +# 1. 生成 .env (参照 .env.example; POWERSYNC_JWT_KEY_B64 由 secret 派生) +umask 077 +PG_PASSWORD=$(openssl rand -hex 16) +PS_STORAGE_PASSWORD=$(openssl rand -hex 16) +JWT_SECRET_021=$(openssl rand -hex 32) +POWERSYNC_JWT_SECRET=$(openssl rand -hex 32) +POWERSYNC_JWT_KEY_B64=$(printf %s "$POWERSYNC_JWT_SECRET" | openssl base64 -A | tr '+/' '-_' | tr -d '=') +cat > .env < 从现网 MariaDB 只读快照导入 +# 现网连接串从 iboard 容器环境变量读取 (不回显), 源库仅 SELECT +set -e +cd /app/iboard-0.2.1/deploy/0.2.1 +SRC=$(docker inspect iboard --format '{{range .Config.Env}}{{println .}}{{end}}' | grep '^DATABASE_URL=' | cut -d= -f2-) +[ -n "$SRC" ] || { echo '未取到现网 DATABASE_URL'; exit 1; } + +echo '--- truncate 0.2.1 库 ---' +docker compose exec -T pg psql -U iboard021 -d iboard021 -c "TRUNCATE TABLE \"User\",\"Project\",\"ProjectMember\",\"BoardColumn\",\"Task\",\"TaskMember\",\"TaskTag\",\"Habit\",\"CheckIn\",\"CheckList\",\"CheckListItem\",\"CheckListTemplate\",\"Article\",\"Strategy\",\"Indicator\",\"IndicatorPoint\",\"News\",\"Alert\",\"ServerNode\",\"ServerNodeHistory\",\"ServiceCheck\",\"Econ_SowInventory\",\"ScraperCache\",\"OilPriceHistory\",\"MihomeAccount\",\"MihomeHome\",\"MihomeDevice\",\"MihomeEnvSample\",\"MihomeEnvHour\" RESTART IDENTITY CASCADE;" + +echo '--- etl run ---' +docker compose exec -T -e SRC_DATABASE_URL="$SRC" app node scripts/etl-mariadb-to-pg.mjs diff --git a/deploy/0.2.1/initdb/01-powersync-storage.sh b/deploy/0.2.1/initdb/01-powersync-storage.sh new file mode 100644 index 0000000..3521fb6 --- /dev/null +++ b/deploy/0.2.1/initdb/01-powersync-storage.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# 一次性初始化 (仅首次建库时执行): 创建 powersync bucket storage 专用用户 +# PG >= 14 允许源库与 bucket storage 同实例 (powersync schema 由 service 自建) +set -e +: "${PS_STORAGE_PASSWORD:?PS_STORAGE_PASSWORD missing}" + +psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL + DO \$\$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'powersync_storage') THEN + CREATE USER powersync_storage WITH PASSWORD '$PS_STORAGE_PASSWORD'; + END IF; + END + \$\$; + GRANT CREATE ON DATABASE $POSTGRES_DB TO powersync_storage; +EOSQL + +echo "[initdb] powersync_storage user ready." diff --git a/deploy/0.2.1/initdb/02-publication.sql b/deploy/0.2.1/initdb/02-publication.sql new file mode 100644 index 0000000..2911281 --- /dev/null +++ b/deploy/0.2.1/initdb/02-publication.sql @@ -0,0 +1,10 @@ +-- 一次性初始化: PowerSync 逻辑复制所需 publication (服务不自建, 见 PSYNC_S1141) +-- 仅发布任务域 + User; 新增同步表时需 ALTER PUBLICATION powersync ADD TABLE ... +CREATE PUBLICATION powersync FOR TABLE + "Project", + "ProjectMember", + "BoardColumn", + "Task", + "TaskMember", + "TaskTag", + "User"; diff --git a/deploy/0.2.1/powersync/service.yaml b/deploy/0.2.1/powersync/service.yaml new file mode 100644 index 0000000..757674d --- /dev/null +++ b/deploy/0.2.1/powersync/service.yaml @@ -0,0 +1,41 @@ +# PowerSync Service 主配置 (0.2.1 验证部署) +# 参考: https://docs.powersync.com/configuration/powersync-service/self-hosted-instances +# 注意: !env 是 YAML 整值标签, 只能整个值替换变量, 不能内嵌在 URI 字符串中; +# 完整连接串由 docker-compose 以 PS_* 环境变量注入。 + +replication: + connections: + - type: postgresql + # 源库: 本栈 pg 容器 (0.2.1 独立实例), 复制走 iboard021 账号 + uri: !env PS_SOURCE_URI + sslmode: disable + +# bucket storage: 同实例 PG (16 >= 14 允许同库不同 schema, service 自建 powersync schema) +storage: + type: postgresql + uri: !env PS_STORAGE_URI + sslmode: disable + +port: 8080 + +sync_config: + path: /config/sync-config.yaml + +# 客户端鉴权: app 后端 /api/powersync/token 签发 HS256 JWT (aud=powersync) +# oct 密钥 k = base64url(POWERSYNC_JWT_SECRET) +client_auth: + jwks: + keys: + - kty: oct + k: !env PS_JWT_KEY_B64 + alg: HS256 + audience: + - powersync + +telemetry: + disable_telemetry_sharing: true + +system: + logging: + level: info + format: text diff --git a/deploy/0.2.1/powersync/sync-config.yaml b/deploy/0.2.1/powersync/sync-config.yaml new file mode 100644 index 0000000..041834e --- /dev/null +++ b/deploy/0.2.1/powersync/sync-config.yaml @@ -0,0 +1,25 @@ +# Sync Streams (edition 3): 任务模块 6 表 + User (展示用) +# Sequential ID Mapping (见 docs/V0.2/离线编辑模块/0.2.1实施方案.md §3): +# - 客户端主键 = uuid 列 (离线新建行同步回填后 id 稳定); int 自增 id 不下发 +# - 外键输出 uuid 镜像列, 别名回 projectId/columnId/parentId/taskId (客户端字段名不变) +# - 引号保留 PG PascalCase 标识; 可见性按 ProjectMember 过滤 + +config: + edition: 3 + +streams: + iboard_users: + auto_subscribe: true + query: SELECT CAST("id" AS TEXT) AS "id", "name", "email" FROM "User" + + iboard_task_data: + auto_subscribe: true + with: + my_projects: SELECT "projectId" FROM "ProjectMember" WHERE CAST("userId" AS TEXT) = auth.user_id() + queries: + - SELECT "uuid" AS "id", "uuid", "code", "name", CAST("ownerId" AS TEXT) AS "ownerId", "isPersonal", "archived", "notes", "taskKinds", "icon", "autoArchive", "createdAt", "updatedAt" FROM "Project" WHERE "id" IN my_projects + - SELECT "uuid" AS "id", "uuid", "projectUuid" AS "projectId", CAST("userId" AS TEXT) AS "userId", "permission", "createdAt" FROM "ProjectMember" WHERE "projectId" IN my_projects + - SELECT "uuid" AS "id", "uuid", "projectUuid" AS "projectId", "name", "sortOrder", "boundStatus", "retention", "createdAt", "updatedAt" FROM "BoardColumn" WHERE "projectId" IN my_projects + - SELECT "uuid" AS "id", "uuid", "projectUuid" AS "projectId", "columnUuid" AS "columnId", "parentUuid" AS "parentId", "title", "description", "kind", "markDate", "status", "doneAt", CAST("assigneeId" AS TEXT) AS "assigneeId", CAST("creatorId" AS TEXT) AS "creatorId", "extras", "sortOrder", "number", "archived", "archivedAt", "archiveSource", "createdAt", "updatedAt" FROM "Task" WHERE "projectId" IN my_projects + - SELECT "uuid" AS "id", "uuid", "taskUuid" AS "taskId", CAST("userId" AS TEXT) AS "userId" FROM "TaskMember" WHERE "taskId" IN (SELECT "id" FROM "Task" WHERE "projectId" IN my_projects) + - SELECT "uuid" AS "id", "uuid", "taskUuid" AS "taskId", "name" FROM "TaskTag" WHERE "taskId" IN (SELECT "id" FROM "Task" WHERE "projectId" IN my_projects) diff --git a/deploy/0.2.1/tls/nginx.conf b/deploy/0.2.1/tls/nginx.conf new file mode 100644 index 0000000..22f61e1 --- /dev/null +++ b/deploy/0.2.1/tls/nginx.conf @@ -0,0 +1,37 @@ +# 0.2.1 TLS 前置: 单端口 13443, 按路径分流 (自签证书, 见 README) +# PowerSync 页面必须在安全上下文运行 (navigator.locks 仅 HTTPS/localhost), 故 app 与 sync 统一走 TLS +server { + listen 443 ssl; + server_name bh.vps.honor3.com; + + ssl_certificate /etc/nginx/certs/bh.pem; + ssl_certificate_key /etc/nginx/certs/bh.key; + + # PowerSync (HTTP 流式 + WebSocket 兜底) + location /sync/ { + proxy_pass http://powersync:8080; + proxy_http_version 1.1; + proxy_set_header Connection ''; + proxy_buffering off; + proxy_read_timeout 3600s; + } + location /socket/ { + proxy_pass http://powersync:8080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 3600s; + } + location /health { + proxy_pass http://powersync:8080; + } + + # 应用 (静态前端 + API) + location / { + proxy_pass http://app:13001; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + } +} diff --git a/docs/环境监控模块设计.md b/docs/V0.1/环境监控模块设计.md similarity index 100% rename from docs/环境监控模块设计.md rename to docs/V0.1/环境监控模块设计.md diff --git a/docs/V0.2/任务模块/任务模块功能设计.md b/docs/V0.2/任务模块/任务模块功能设计.md new file mode 100644 index 0000000..79f2cd8 --- /dev/null +++ b/docs/V0.2/任务模块/任务模块功能设计.md @@ -0,0 +1,399 @@ +# iBoard V0.2 任务模块 功能设计 + +| 项 | 内容 | +| --- | --- | +| 版本 | V0.2(对应分支 `dev-0.2`,应用版本 `0.2.0`) | +| 文档状态 | **设计基线**(待评审) | +| 上游文档 | `任务模块需求文档.md`(**149 条需求点**,编号 `T-*`) | +| 配套文档 | `任务模块设计文档-框架设计.md`、`任务模块数据库设计文档.md`、`任务模块界面设计.html` | +| 对应关系 | **本文与需求点一一对应**:每条需求点均给出「设计落点(功能/组件/接口/表字段)」与「实现要点」 | + +> **阅读方式** +> 1. 本文按需求文档的模块码组织(`VW` / `COL` / `BD` / `FL` / `TK` / `AS` / `ST` / `PJ` / `PM` / `CP` / `UI` / `DT` / `NF` / `EX`)。 +> 2. 每条需求点一行:**编号 → 设计落点 → 实现要点**。设计落点中的组件名、接口路径、表字段均与《任务模块数据库设计文档》《任务模块界面设计》一致。 +> 3. 文末 §15 给出**覆盖自检表**,确保 149 条无遗漏。 + +--- + +## 0. 通用约定 + +### 0.1 契约命名 + +| 类别 | 约定 | 示例 | +| --- | --- | --- | +| 状态枚举 | `todo` / `doing` / `done` | 未开始 / 执行中 / 完成 | +| 列 | `BoardColumn` 实体,按 `sortOrder` 升序 | 默认 `Plan/Doing/Action/Done` | +| 保留选项 | `never` / `today` / `3d` / `week` / `month` / `forever` | 永不显示 / 当天 / 3 天 / 本周 / 本月 / 永久显示 | +| 视图 | `list` / `board` | 设备端记忆 | +| 设备端键 | `iboard_task_prefs` | `{ lastProjectId, viewMode: { [projectId]: 'list'\|'board' } }` | + +### 0.2 接口总览 + +| 方法 | 路径 | 用途 | +| --- | --- | --- | +| GET | `/api/projects` | 我参与的项目(含个人默认项目、归档标记) | +| POST | `/api/projects` | 新建普通项目(同时初始化 4 个默认列) | +| GET | `/api/projects/:id` | 项目详情(含成员、列配置) | +| PUT | `/api/projects/:id` | 改名 / 归档 | +| DELETE | `/api/projects/:id` | 删除项目(个人项目 400) | +| POST | `/api/projects/:id/members` | 加成员(个人项目 400) | +| DELETE | `/api/projects/:id/members/:uid` | 移成员 / 自己退出 | +| GET/POST | `/api/projects/:id/columns` | 列列表 / 新增列 | +| PUT | `/api/columns/:id` | 改名 / 改绑定 / 改保留选项 | +| DELETE | `/api/columns/:id` | 删列(需 `moveToColumnId`,最后列 400) | +| PUT | `/api/projects/:id/columns/order` | 列排序 | +| GET | `/api/projects/:id/tasks` | 任务列表(含子任务与列信息) | +| POST | `/api/projects/:id/tasks` | 新建任务 / 子任务 | +| GET | `/api/tasks/:id` | 任务详情 | +| PUT | `/api/tasks/:id` | 改状态 / 换列 / 改字段(触发裁决) | +| DELETE | `/api/tasks/:id` | 删除任务(级联子任务) | +| POST | `/api/tasks/:id/copy` | 跨项目拷贝 | + +--- + +## 1. 视图体系(T-VW) + +| 编号 | 设计落点 | 实现要点 | +| --- | --- | --- | +| T-VW-01 | `ProjectView.jsx` 内 `viewMode` 分支渲染 `ListView` / `BoardView` | 两视图共用 `TaskStore` 的同一份任务数据,仅渲染方式不同 | +| T-VW-02 | `SwipeTabs`/分段控件切换 `viewMode` | 纯本地状态切换,不触发路由跳转、不重新拉取数据(满足"无整页刷新") | +| T-VW-03 | `prefs.js` → `viewMode[projectId]` | 按项目 id 分键存储,A/B 项目互不影响 | +| T-VW-04 | `ProjectView` 挂载时读 `prefs.getViewMode(projectId)` | 有值用之;无值走 T-VW-05 默认 | +| T-VW-05 | 默认值 `'list'` | 新项目首次进入为列表 | +| T-VW-06 | `localStorage.iboard_task_prefs` | **不落库、不同步**;换设备不继承(与 T-FL-15 的项目级配置区分) | +| T-VW-07 | `prefs.getLastProjectId()` / `setLastProjectId()` | 进入 `/task` 时读取;无记录或记录的项目已不可见 ⇒ 回落个人默认项目 | +| T-VW-08 | `TaskPage` 首屏上部 `RecentProjects` 条 | 取最近访问的 3 个项目(按设备端访问时间倒序),末尾「更多项目」入口 → `/task/list`(完整项目列表 `ProjectList`,从「更多」进入) | +| T-VW-09 | `TaskPage` 首屏下部直接内嵌 `ProjectView` | 当前项目 = 上次访问项目(无记录 ⇒ 个人默认项目);点首屏项目卡片切换当前项目,下部整体切换,不跳转页面 | +| T-VW-10 | `prefs.js` 增 `recentOrder[]`(设备端) | 长按卡片拖动排序并持久化;无手动记录时按最近访问时间自动排列;项目不可见时从记录剔除 | + +> **首屏结构调整(T-VW-08/09)**:`TaskPage`(`/task`)不再以项目列表为首屏,改为「最近项目条 + 当前项目 `ProjectView`」;完整项目列表迁移至 `/task/list`。旧路由 `/task/:pid` 保留(打开指定项目),供深链使用。 + +> **实现细节**:`lastProjectId` 指向的项目若已被退出/删除,视为"无记录",静默回落到个人默认项目,不弹错。 + +--- + +## 2. 看板列管理(T-COL) + +| 编号 | 设计落点 | 实现要点 | +| --- | --- | --- | +| T-COL-01 | `POST /api/projects` 内部初始化 4 列 | 事务内创建 `Plan(todo, sort 0)`、`Doing(doing,1)`、`Action(null,2)`、`Done(done,3)` | +| T-COL-02 | `ColumnSettings` → `POST /api/projects/:id/columns` | `sortOrder = max+1` 追加末尾;`boundStatus=null`、`retention='forever'`(B-18) | +| T-COL-03 | `PUT /api/columns/:id` | 改 `name`;前端 `TaskStore` 整体替换列列表后重渲染 | +| T-COL-04 | `DELETE /api/columns/:id?moveToColumnId=` | 二次确认弹窗;见 T-EX-16 | +| T-COL-05 | `PUT /api/projects/:id/columns/order` | 传完整 `[columnId…]` 数组,服务端按索引重写 `sortOrder` | +| T-COL-06 | 服务端校验总量 | `1 <= count <= 10`;达上限拒绝新增(T-EX-19)、仅剩 1 列拒绝删除(T-EX-17) | +| T-COL-07 | `BoardColumn.name` 校验 | trim 后非空、长度 ≤ 12;`@@unique([projectId,name])` 兜底重名 409 | +| T-COL-08 | 列删除即绑定消失 | 绑定存在列记录上,列删则解除,无需额外清理 | +| T-COL-09 | `requireProjectMember` | 项目级配置,任一成员可改;不影响其他项目 | +| T-COL-10 | `ListView` 不分组但仍显示列信息 | 任务行内以小标签显示所属列名(可关闭),归属始终正确 | + +--- + +## 3. 看板与列-状态绑定(T-BD) + +> 联动的**唯一实现**在 `server/task-domain/resolveStatus.js`,前端只做乐观更新。判定矩阵见《框架设计》§2.3。 + +| 编号 | 设计落点 | 实现要点 | +| --- | --- | --- | +| T-BD-01 | `BoardView` 按 `columns` 数组渲染列 | 列来自项目配置,可增删改排序 | +| T-BD-02 | `ColumnSettings` 每列一组「绑定开关 + 状态下拉」 | 写入 `BoardColumn.boundStatus`(`null`/`todo`/`doing`/`done`) | +| T-BD-03 | 无唯一约束限制多列绑定同状态 | 允许 `done` 与 `action` 同为 `done` | +| T-BD-04 | `onColumnChange()` | 目标列 `boundStatus != null` ⇒ `status = boundStatus` | +| T-BD-05 | `onStatusChange()` | 状态变更后若当前列已匹配则不动作,否则走归位 | +| T-BD-06 | 归位取列序首个匹配 | `columns` 按 `sortOrder` 排序后 `find()` 首个 | +| T-BD-07 | **只看目标列** | 目标列不绑定 ⇒ 保留原状态;**无"拖出回退"规则** | +| T-BD-08 | 不绑定列完全正交 | 拖入/拖出、改状态均不改该列以外语义 | +| T-BD-14 | `resolveColumn(task, newStatus)` | 当前列 `boundStatus !== newStatus` ⇒ 触发归位 | +| T-BD-15 | 按当前 `sortOrder` 取首个 | 用户调序后归位目标随之改变 | +| T-BD-16 | 无匹配返回 `null` | `null` ⇒ 不改 `columnId`,状态照常写库 | +| T-BD-09 | 初始化默认值 | `Plan=todo`、`Doing=doing`、`Done=done`、`Action=null` | +| T-BD-10 | 项目级配置 | 任一成员可改;不同项目独立 | +| T-BD-11 | 配置变更不回溯 | 保存配置时**不触发**任何任务更新语句 | +| T-BD-12 | 保存后返回最新列列表 | 前端整体替换 + 重算可见集合,无需刷新 | +| T-BD-13 | `resolveStatus()` 首行判 `parentId` | 子任务直接跳过全部联动 | + +--- + +## 4. 已完成任务显示控制(T-FL) + +| 编号 | 设计落点 | 实现要点 | +| --- | --- | --- | +| T-FL-01 | `BoardColumn.retention` | 每列独立字段 | +| T-FL-02 | `ColumnSettings` 下拉 6 项 | 固定枚举,无自由输入 | +| T-FL-03 | `retention='forever'` | 判定恒为可见 | +| T-FL-04 | `retention='never'` | 已完成任务恒不可见 | +| T-FL-05 | `retention='today'` | `doneAt >= 今天 00:00` | +| T-FL-06 | `retention='3d'` | `doneAt >= 今天-2天 00:00` | +| T-FL-07 | `retention='week'` | `doneAt >= 本周一 00:00` | +| T-FL-08 | `retention='month'` | `doneAt >= 本月1日 00:00` | +| T-FL-09 | `task-domain/retention.js` + 前端定时重算 | 超出窗口自动隐藏,无需操作 | +| T-FL-10 | 判定字段 `Task.doneAt` | 与当前列无关;取消完成时 `doneAt=null` | +| T-FL-11 | 前端可见性统一走同一函数 | 列表与看板共用 `isTaskVisible(task, column)`;列表不分组但归属不变 | +| T-FL-12 | 判定前置 `status !== 'done'` | 未完成任务恒可见 | +| T-FL-13 | 纯展示层过滤 | 不修改任何字段;接口返回全量,前端过滤(或服务端按需过滤但不改数据) | +| T-FL-14 | 列计数取过滤后集合长度 | 列头计数与卡片数同源,杜绝不一致 | +| T-FL-15 | 存在 `BoardColumn` 上 | 项目级共享,非个人偏好(对比 T-VW-06 设备端) | + +> **实现细节**:保留判定在前后端各有一份实现(服务端用于分页/统计,前端用于即时过滤),但**必须共用同一口径常量**,跨度边界由 `date.js` 单测覆盖。 + +--- + +## 5. 任务字段体系(T-TK) + +| 编号 | 设计落点 | 实现要点 | +| --- | --- | --- | +| T-TK-01 | `Task` 表 | 字段:`title`/`description`/`projectId`/`columnId`/`status`/`assigneeId`/`creatorId`/`doneAt`/`sortOrder` + 关联成员/标签/扩展 | +| T-TK-02 | `title` 非空校验 | 其余字段允许空;trim 后为空 ⇒ 400 | +| T-TK-03 | `status` 枚举三值 | DB 层用 `VARCHAR(8)` + 应用层校验(避免 ENUM 改值困难) | +| T-TK-04 | **已移除 UI**(2026-10-24):`ExtrasCard` 组件删除;信息记录由项目信息块(T-PJ-13)承载 | +| T-TK-05 | **随 T-TK-04 移除**;`extras` 字段在 DB 保留不再使用 | +| T-TK-06 | **随 T-TK-04 移除** | +| T-TK-07 | **随 T-TK-04 移除** | +| T-TK-08 | `Task.assigneeId` 单值 | 重复设置以最后一次为准 | +| T-TK-09 | 新建时服务端默认赋值 | 未显式传 `assigneeId` ⇒ 取 `req.user.id`(B-10) | +| T-TK-10 | `assigneeId` 允许 `null` | 显式传 `null` 即清空,UI 显示「未指派」 | +| T-TK-11 | `TaskMember` 关联表 | 多对多,`@@unique([taskId,userId])` 去重 | +| T-TK-12 | `TaskTag` 表 + 卡片标签渲染 | 多标签,展示于卡片/条目 | +| T-TK-13 | 服务端维护 | `creatorId`/`createdAt` 不可改;`updatedAt` 自动刷新 | +| T-TK-14 | 标题 30 字上限 + 快速添加截断 | 服务端 create/update 校验 `title ≤ 30`;前端 `quickAdd.js#splitTitle`:≤30 原样,超长在 30 字内(≥8 字)找最后一个分隔符截断,前段为标题、**完整原文**写入 `description`;快速添加输入框 `maxLength=200` | +| T-TK-15 | `Task.markDate DATETIME(3) NULL`(迁移 20261025120000) | create/update 接受 `YYYY-MM-DD`(空/null 清除),完整日期存储;详情页 DatePicker(precision=day)选择/清除;卡片展示 `MM-dd` 小标签(`date-tag`) | +| T-TK-16 | `quickAdd.js#parseTimePrefix` | 前缀词表:今天/明天/后天/本周/下周/本月/下月(须后跟分隔符或结束);今天=当日,明天/后天=+1/+2,本周=本周日、下周=下周日(周一为一周开始),本月/下月=当月/下月最后一天;命中后前缀移出标题并写入 `markDate` | + +--- + +## 6. 负责人 & 成员(T-AS) + +| 编号 | 设计落点 | 实现要点 | +| --- | --- | --- | +| T-AS-01 | `TaskCard` 负责人区 | 字号 13px/600 + 头像,位于标题下方主信息位 | +| T-AS-02 | `TaskCard` 成员区 | 字号 11px/400、色 `#999`,位于卡片底部 | +| T-AS-03 | `UserPicker` 数据源 = 项目成员 | 只列 `ProjectMember` 中的用户 | +| T-AS-04 | 同上(多选模式) | 不可选项目外用户 | +| T-AS-05 | `assigneeId === null` 时渲染「未指派」 | 占位样式弱化,可点击再次指派 | +| T-AS-06 | 渲染时 `LEFT JOIN ProjectMember` 判定 | 非成员 ⇒ 显示「已退出」灰标;见 T-EX-06 | +| T-AS-07 | 权限模型 | 无"负责人可编辑"概念,所有成员全读写(T-PM-02) | + +--- + +## 7. 子任务体系(T-ST) + +| 编号 | 设计落点 | 实现要点 | +| --- | --- | --- | +| T-ST-01 | `Task.parentId` 自关联 | 创建子任务时校验:父任务 `parentId IS NULL`,否则 400 | +| T-ST-02 | 一致性校验 | 子任务 `projectId` 必须等于父任务 `projectId`,否则 400(T-EX-12) | +| T-ST-03 | 状态独立 | 父/子状态字段各自独立,无级联更新 | +| T-ST-04 | `SubtaskRow` 缩进 | 左侧 28px 缩进 + 层级线 | +| T-ST-05 | 折叠/展开按钮 | 组件内存状态,折叠时不渲染子项 | +| T-ST-06 | `TaskCard` 内嵌 `SubtaskRow` 列表 | 简洁单行:复选框 + 标题,最多显示 3 条 + 「查看全部」 | +| T-ST-07 | 卡片内直接勾选 | 勾选调 `PUT /api/tasks/:id {status}`;完整编辑走 `TaskDetail` 弹窗 | +| T-ST-08 | 子任务跳过绑定 | 见 T-BD-13;父任务完成时子任务状态不变 | +| T-ST-09 | `SubtaskRow` 带 `StatusDot` | 三色小圆点:灰=未开始 / 蓝=执行中 / 绿=完成 | +| T-ST-10 | 看板只渲染顶层任务 | 看板查询 `WHERE parentId IS NULL` | +| T-ST-11 | `Task.parentId` 级联删除 | `onDelete: Cascade`(T-EX-07) | +| T-ST-12 | `useLongPressDrag` 落点为目标顶层任务 | 设置 `parentId` | +| T-ST-13 | 目标为子任务 ⇒ 拒绝 | Toast「仅支持一级子任务」 | +| T-ST-14 | 被拖任务有子任务 ⇒ 拒绝 | 避免二级嵌套 | +| T-ST-15 | 保持原列与状态 | 仅更新 `parentId`,不触发状态裁决 | + +--- + +## 8. 项目体系(T-PJ) + +| 编号 | 设计落点 | 实现要点 | +| --- | --- | --- | +| T-PJ-01 | 用户首次访问自动创建 | 按 `isPersonal=true` 且 `ownerId=userId` 幂等创建 | +| T-PJ-02 | `DELETE /api/projects/:id` 校验 `isPersonal` | 为真 ⇒ 400(T-EX-09) | +| T-PJ-03 | `POST .../members` 校验 `isPersonal` | 为真 ⇒ 400 | +| T-PJ-04 | `name` 初始 `${user.name}的任务` | 允许 `PUT` 改名,`isPersonal` 不变 | +| T-PJ-05 | `POST /api/projects` | 创建者自动写入 `ProjectMember` | +| T-PJ-06 | 成员接口 | 增/删成员,即时影响可见性 | +| T-PJ-07 | `DELETE .../members/:uid` 允许 `uid == ownerId` | 创建人退出后不再是成员 ⇒ 不可见 | +| T-PJ-08 | 删除最后成员不删项目 | 仅删 `ProjectMember`(B-11);见 T-EX-11 | +| T-PJ-09 | `Project.archived` | 列表分「进行中 / 已归档」 | +| T-PJ-10 | 列表查询按成员关系过滤 | `WHERE EXISTS ProjectMember(userId)` | +| T-PJ-11 | 新组件 `ProjectManage`(项目管理页) | 分区承载:成员(复用 `ProjectMembers` 逻辑)、列设置(复用 `ColumnSettings`)、项目信息;`ProjectView` 头部仅保留「项目管理」一个入口 | +| T-PJ-12 | `ProjectManage` 的「项目信息」分区 | 改名(`PUT /api/projects/:id {name}`)、归档/取消归档(`{archived}`)、删除(个人项目隐藏入口)、退出(仅创建人可见);删除与退出均有二次确认 | +| T-PJ-13 | `Project.notes` Json + `ProjectManage` 的「信息块」分区 | 存 `[{title, content}]`(≤50 块,标题 ≤50 字、内容 ≤20000 字);`PUT {notes}` 整体替换;底部弹层独立编辑(标题输入 + 多行 textarea + 删除),键盘弹出时复用 `useKeyboardInset` 抬升 | + +--- + +## 9. 项目权限与入口(T-PM) + +| 编号 | 设计落点 | 实现要点 | +| --- | --- | --- | +| T-PM-01 | `requireProjectMember` 中间件 | 非成员 403;列表查询天然过滤 | +| T-PM-02 | 无角色区分 | 所有成员对任务/列/成员均有全读写 | +| T-PM-03 | `ProjectMember.permission` 字段 | 本期恒为 `member`,预留分级扩展位 | +| T-PM-04 | 归档接口无权限限制 | 任意成员可归档(`PUT /api/projects/:id {archived}`) | +| T-PM-05 | 复用 `authRequired` | 未登录 401 | +| T-PM-06 | `BottomNav.TABS` 插入 `{ key:'/task', label:'任务' }` | 位于 `打卡` 之后;`HomePath` 仍为 `/checkin` | +| T-PM-07 | `prefs.lastProjectId` | 设备端;无记录 ⇒ 个人默认项目 | + +--- + +## 10. 跨项目拷贝(T-CP) + +| 编号 | 设计落点 | 实现要点 | +| --- | --- | --- | +| T-CP-01 | `POST /api/tasks/:id/copy` | 复制而非移动,源任务零改动 | +| T-CP-02 | `copyTask()` 字段映射 | 标题/描述/状态/列/扩展/标签/成员 全量复制 | +| T-CP-03 | 递归复制子任务 | 只复制一层(子任务无更深层级) | +| T-CP-04 | `creatorId = req.user.id` | 覆盖源创建人 | +| T-CP-05 | 无任何关联字段 | 拷贝后两条独立记录 | +| T-CP-06 | 校验目标项目成员身份 | 非成员 403 | +| T-CP-07 | 人员交集过滤 | 保留属于目标项目成员者,返回 `dropped` 名单(T-EX-08) | +| T-CP-08 | 子任务 `projectId` 同步改写 | 与父任务一致 | +| T-CP-09 | 状态与列沿用不纠正 | 见 T-EX-14 | + +--- + +## 11. UI 交互规范(T-UI) + +| 编号 | 设计落点 | 实现要点 | +| --- | --- | --- | +| T-UI-01 | 列表条目结构 / 快速新增 / 子任务折叠 | 参考 Todoist:底部输入框回车即建 | +| T-UI-02 | `TaskCard` 精简版 | 仅标题、状态点、负责人、子任务摘要;无边框装饰 | +| T-UI-03 | 单 `TaskStore` | 任一视图修改写入 store,另一视图读取同一数据 | +| T-UI-04 | 视图切换纯本地 | 不重新请求,不重载页面 | +| T-UI-05 | 复用 `.card` 与安全区变量 | 375~430px 无横向滚动;底部预留 TabBar 高度 | +| T-UI-06 | 勾选即时变色 | 乐观更新,先改 UI 再发请求 | +| T-UI-07 | `useLongPressDrag` | 长按 300ms 拖拽 + 「⋯」菜单「移动到」兜底 | +| T-UI-08 | `EmptyState` 组件 | 无项目 / 无任务 / 无子任务三档空态 + CTA | +| T-UI-09 | `LoadingState` 骨架屏 | 首屏与切换视图 | +| T-UI-10 | `ErrorState` 组件 | 展示错误 + 重试按钮 | +| T-UI-11 | 弹层容器统一 `padding-bottom: calc(env(safe-area-inset-bottom) + 20px)` | 底部按钮(删除等)与屏幕底边保持安全距离;小屏下弹层 `max-height` + 内部滚动,底部按钮始终完整可见 | + +--- + +## 12. 数据需求(T-DT) + +| 编号 | 设计落点 | 实现要点 | +| --- | --- | --- | +| T-DT-01 | `Project` / `ProjectMember` | 见《数据库设计文档》 | +| T-DT-02 | `BoardColumn.boundStatus` | 每列独立字段,允许多列同值 | +| T-DT-03 | `BoardColumn.retention` | 六档枚举字符串 | +| T-DT-04 | `Task` | 含 `parentId` 自关联、`assigneeId`、`status`、`columnId`、`extras` | +| T-DT-05 | `TaskMember` / `TaskTag` | 关联表;`extras` 为 JSON | +| T-DT-06 | 设备端 | 不落库 | +| T-DT-07 | 索引 | `(projectId, columnId)`、`(projectId, status)`、`(parentId)` | +| T-DT-08 | 迁移文件 | `prisma migrate deploy` 流程 | +| T-DT-09 | `Project.archived` / 任务无物理删 | 归档保留历史 | + +--- + +## 13. 非功能需求(T-NF) + +| 编号 | 设计落点 | 实现要点 | +| --- | --- | --- | +| T-NF-01 | 单请求聚合 + 乐观更新 | 见《框架设计》§4.2 | +| T-NF-02 | 不依赖桌面专有 API | 触控事件统一 `touch` / `pointer` 兼容 | +| T-NF-03 | `src/pages/task/*` + `server/routes/*.js` | 与既有模块结构一致 | +| T-NF-04 | 路由挂载于 `server/index.js` | 沿用既有组织方式 | +| T-NF-05 | 千级任务可用 | 索引 + 分页/虚拟滚动 | + +--- + +## 14. 异常与边界(T-EX) + +| 编号 | 设计落点 | 实现要点 | +| --- | --- | --- | +| T-EX-01 | 前端 500ms 防抖 + 服务端幂等判定 | 相同终态请求直接返回当前任务 | +| T-EX-02 | 乐观更新快照回滚 | 失败恢复快照 + Toast + 重试按钮 | +| T-EX-03 | 列配置改为不绑定 | 已有任务保持不动 | +| T-EX-04 | 状态与列不匹配 | 走 T-BD-14~16 归位 | +| T-EX-05 | 拖到不绑定列 | 状态保留,不回退 | +| T-EX-06 | 非成员渲染「已退出」 | 见 T-AS-06 | +| T-EX-07 | 级联删除子任务 | 确认弹窗提示条数 | +| T-EX-08 | 拷贝丢弃人员 | 返回 `dropped` 并提示 | +| T-EX-09 | 个人项目两个保护接口 | 400 + 明确文案 | +| T-EX-10 | 列空态 | 展示该列空态,不报错 | +| T-EX-11 | 无成员项目 | 静默不可见,无特判报错 | +| T-EX-12 | 子任务跨项目 | 服务端 400 | +| T-EX-13 | 拖拽降级 | 保持原列与状态 | +| T-EX-14 | 拷贝状态/列无法对应 | 沿用源值,不纠正 | +| T-EX-15 | 拖到自身或后代 | 拒绝 + Toast | +| T-EX-16 | 删除有任务的列 | 二次确认 + 迁移到指定列(默认相邻)+ 重新裁决 | +| T-EX-17 | 删除最后一列 | 400「至少保留一列」 | +| T-EX-18 | 跨周期当场消失 | 定时重算 + 退场动画 + 重排 | +| T-EX-19 | 达到列数上限 | 拒绝新增 + 提示 | + +--- + +## 15. 任务归档(T-ARC) + +> 归档与 T-FL 显示控制的分层:**隐藏 = 展示过滤(可逆、不落库)**;**归档 = 移出主工作集(落库、可恢复)**。 +> 自动归档的倒计时起点是任务**被隐藏的时刻**(由 `doneAt + 列保留窗口` 推导,不新增 hiddenAt 字段)。 + +### 15.1 数据 + +| 落点 | 说明 | +| --- | --- | +| `Project.autoArchive VARCHAR(8) NOT NULL DEFAULT 'never'` | 自动归档档位:`3d` / `1w` / `1m` / `never`;项目级共享(同 T-FL-12 口径) | +| `Task.archived BOOLEAN NOT NULL DEFAULT false` | 归档标记;归档**不迁移表**,主查询统一追加 `archived = false` | +| `Task.archivedAt DATETIME(3) NULL` | 归档时刻(查询过滤与排序键) | +| `Task.archiveSource VARCHAR(8) NULL` | `'manual'` / `'auto'`,溯源用 | +| 索引 | `idx_task_project_archived (projectId, archived, archivedAt)`;`idx_task_project_status_archived (projectId, status, archived)` | +| 迁移(待实施) | `20261026000000_task_archive`:三列 + 两索引(MySQL DDL 见数据库设计文档 §3.1/§3.4) | + +### 15.2 API + +| 接口 | 说明 | +| --- | --- | +| `POST /api/tasks/:id/archive` | 手动归档:父任务连带全部后代置 `archived/archivedAt/archiveSource='manual'`;幂等(已归档直接返回当前态) | +| `POST /api/tasks/:id/unarchive` | 恢复:父任务连带后代清除归档三字段;原列因 `ON DELETE RESTRICT` 必存在,状态保持不变 | +| `GET /api/projects/:id/tasks/archived?keyword&kind&columnId&from&to&cursor&limit` | 归档查询:`keyword` 标题 LIKE、`kind`、`columnId`、`from/to`(归档日期,含边界);`limit` 默认 20、≤100;游标 = `archivedAt+id` 复合降序,返回 `{ items, nextCursor }`,`nextCursor` 为空即到底 | +| `PUT /api/projects/:id` | 增接受 `autoArchive`,枚举校验(非法 400) | +| 既有查询 | `GET /projects/:id/tasks`、统计、列计数等全部追加 `archived: false`;`taskCount`(projectSummary)同口径 | + +### 15.3 自动归档执行 + +- **归档到期判定**:`due = 隐藏时刻 + autoArchive 档位`;隐藏时刻 = `doneAt + 列保留窗口`(复用 `retentionStart` 口径,含 `today/3d/week/month`;`forever` 列任务永不隐藏、不进入自动归档)。 +- **执行时机(双保险)**: + 1. **读时惰性**:加载项目任务时对该项目先执行一轮到期归档(保证用户看到的数据与判定一致,刚到期的任务当场消失); + 2. **每小时 cron 兜底**:扫描全部 `autoArchive != 'never'` 项目的到期 done 任务批量归档(`archiveSource='auto'`)。 +- **幂等**:`archived = false` 且 `status = 'done'` 且 `doneAt` 非空才进入判定;重复执行无副作用。 +- **批量写**:cron 用 `updateMany`(按到期集合),读时惰性用单项目范围 `updateMany`。 + +### 15.4 前端 + +| 落点 | 说明 | +| --- | --- | +| 任务 `⋯` 菜单 | 新增「归档」(`Dialog.confirm`,含子任务时提示连带条数);成功后列表/看板乐观移除 | +| 项目管理 | 「归档设置」分区:`Selector` 四档(3 天后/一周后/一月后/永不),保存即生效 | +| 项目视图头部 | 项目栏新增「已归档」入口 → 全屏弹层:过滤条(关键词/类型/列/归档时段)+ 分页「加载更多」+ 行内「恢复 / 删除」 | +| 主视图 | 列表/看板无需感知归档(数据源已排除);恢复后经 `load()` 回到原位 | + +### 15.5 边界 + +| 场景 | 处理 | +| --- | --- | +| 归档父任务时新增/恢复子任务 | 子任务随父判定:父 archived 则子不单独出现在主列表(查询按父排除) | +| 自动归档期间用户正在编辑该任务 | 详情弹层保存正常(归档不锁写);保存后任务仍在归档集 | +| 恢复后任务再次到期隐藏 | 重新进入自动归档倒计时(`doneAt` 未变则可能立即再次到期,属预期) | +| 「永不」档位 | 不做任何自动归档;手动归档不受影响 | +| 拷贝任务 | 拷贝件不继承归档态(`copyTask` 不复制 archived 三字段) | + +--- + +## 16. 覆盖自检表 + +| 模块 | 编号范围 | 条数 | 本文对应章节 | +| --- | --- | --- | --- | +| 视图体系 | T-VW-01 ~ 10 | 10 | §1 | +| 看板列管理 | T-COL-01 ~ 10 | 10 | §2 | +| 列-状态绑定 | T-BD-01 ~ 16 | 16 | §3 | +| 已完成显示控制 | T-FL-01 ~ 15 | 15 | §4 | +| 任务字段体系 | T-TK-01 ~ 13 | 13 | §5 | +| 负责人 & 成员 | T-AS-01 ~ 07 | 7 | §6 | +| 子任务体系 | T-ST-01 ~ 15 | 15 | §7 | +| 项目体系 | T-PJ-01 ~ 13 | 13 | §8 | +| 项目权限与入口 | T-PM-01 ~ 07 | 7 | §9 | +| 跨项目拷贝 | T-CP-01 ~ 09 | 9 | §10 | +| UI 交互规范 | T-UI-01 ~ 11 | 11 | §11 | +| 数据需求 | T-DT-01 ~ 09 | 9 | §12 | +| 非功能需求 | T-NF-01 ~ 05 | 5 | §13 | +| 异常与边界 | T-EX-01 ~ 19 | 19 | §14 | +| 任务归档 | T-ARC-01 ~ 08 | 8 | §15 | +| **合计** | — | **157** | — | + +> 需求文档共 **157 条**需求点,本文逐条给出设计落点,覆盖率 **157/157**。(其中 `T-BD-14~16`、`T-COL-*`、`T-EX-18~19`、`T-VW-08~10`、`T-PJ-11/12`、`T-UI-11`、`T-ARC-*` 为评审后新增,编号不连续,非笔误。) diff --git a/docs/V0.2/任务模块/任务模块数据库设计文档.md b/docs/V0.2/任务模块/任务模块数据库设计文档.md new file mode 100644 index 0000000..9c28732 --- /dev/null +++ b/docs/V0.2/任务模块/任务模块数据库设计文档.md @@ -0,0 +1,467 @@ +# iBoard V0.2 任务模块 数据库设计文档 + +| 项 | 内容 | +| --- | --- | +| 版本 | V0.2(对应分支 `dev-0.2`,应用版本 `0.2.0`) | +| 文档状态 | **设计基线**(待评审,未落库) | +| 数据库 | MariaDB / MySQL 8(Prisma `provider = "mysql"`) | +| 字符集 | `utf8mb4 / utf8mb4_unicode_ci` | +| 时区约定 | 应用层统一 `Asia/Shanghai`,库内 `DATETIME` 存 UTC | +| 相关文件 | `prisma/schema.prisma`、`prisma/migrations/20261024xxxxxx_add_task_module/migration.sql` | +| 关联文档 | `任务模块需求文档.md`、`任务模块设计文档-框架设计.md`、`任务模块功能设计.md`、`任务模块界面设计.html` | + +--- + +## 1. 概述 + +任务模块新增 **6 张表**,并把既有 `User` 表补上反向关系(不改 `User` 结构): + +| 表 | 作用 | 归属 | +| --- | --- | --- | +| `Project` | 项目(隔离与权限单位) | 项目域 | +| `ProjectMember` | 项目成员(权限关系) | 项目域 | +| `BoardColumn` | 看板列(名称 / 顺序 / 状态绑定 / 保留选项) | 看板域 | +| `Task` | 任务与子任务(自关联,一层) | 任务域 | +| `TaskMember` | 任务成员(多人标记) | 任务域 | +| `TaskTag` | 任务标签 | 任务域 | + +### 1.1 设计原则 + +1. **列是实体,不是枚举。** 看板列允许增删改与排序(T-COL),若仍用 `plan/doing/action/done` 枚举,则"改名"无从表达、"排序"只能堆在 JSON 里、"删除列"会留下悬空字符串。因此独立建 `BoardColumn` 表,`Task` 用 `columnId` 外键引用。 +2. **状态与列双落库,互不推导。** `Task.status`(三值)与 `Task.columnId` 是两个正交维度;未绑定的列(默认 `Action`)必须允许二者不一致(T-BD-08、T-BD-16),任何"由状态反推列"或"由列反推状态"的写法都会丢失信息。联动只在写接口中按项目配置即时裁决。 +3. **配置不下沉到任务。** 绑定与保留选项只存在 `BoardColumn` 上,任务不冗余任何配置副本——改配置立即对所有任务生效,且不需要回填。 +4. **按项目隔离。** 所有业务表都可通过 `projectId` 归集,权限校验统一查 `ProjectMember`。 +5. **软删除优先。** 项目用 `archived`;任务删除为级联物理删除(T-ST-11),但删除前有二次确认。 +6. **迁移只增不改。** 新增需求一律新迁移文件,不修改已执行的迁移。 + +--- + +## 2. 概念模型(ER) + +``` + ┌────────────────┐ + │ User │ (既有表, 仅补反向关系) + └───┬────────┬───┘ + │ │ + ┌──────────▼──┐ ┌──▼──────────────┐ + │ProjectMember│ │ TaskMember │ + │(权限关系) │ │ (任务成员) │ + └──────┬──────┘ └──▲──────────────┘ + │ * │ * + │ │ + ┌───▼──────┐ ┌─┴──────┐ + │ Project │1─*│ Task │*─1┌────────────┐ + └───┬──────┘ └───┬────┘ │ BoardColumn│ + │ * │ * └────────────┘ + ┌────▼───────┐ │ ▲ + │BoardColumn │1────*┘ │ + └────────────┘ (columnId) │ + │ + ┌────────┐ │ + │TaskTag │*──────────────────┘ + └────────┘ + + Task 自关联: parentId ── 子任务(一层, 仅顶层任务可作父) +``` + +基数说明: + +| 关系 | 基数 | 级联 | +| --- | --- | --- | +| User → ProjectMember | 1 : N | Cascade | +| Project → ProjectMember | 1 : N | Cascade | +| Project → BoardColumn | 1 : N | Cascade | +| Project → Task | 1 : N | Cascade | +| BoardColumn → Task | 1 : N | **RESTRICT**(有任务的列不可直接删) | +| Task → Task(parent) | 1 : N | Cascade | +| Task → TaskMember | 1 : N | Cascade | +| Task → TaskTag | 1 : N | Cascade | +| User → Task(assignee / creator) | 1 : N | SetNull / Cascade(见下) | + +> `BoardColumn → Task` 使用 `RESTRICT` 而非 `Cascade`:防止误删列时静默丢任务。删除列必须先在其接口内把任务迁移走(事务内改 `columnId`),迁移完成后再删列。 + +--- + +## 3. 表结构 + +### 3.1 `Project` — 项目 + +| 字段 | 类型 | 空 | 默认 | 说明 | +| --- | --- | --- | --- | --- | +| `id` | `INT` PK AI | 否 | — | 主键 | +| `name` | `VARCHAR(50)` | 否 | — | 项目名 | +| `ownerId` | `INT` | 否 | — | 创建人(可退出项目,故与成员表分离) | +| `isPersonal` | `BOOLEAN` | 否 | `false` | 个人默认项目:不可删、不可加成员(T-PJ-02/03) | +| `archived` | `BOOLEAN` | 否 | `false` | 归档(T-PJ-09) | +| `notes` | `JSON` | 否 | `[]` | 项目信息块 `[{title, content}]`(T-PJ-13) | +| `taskKinds` | `JSON` | **是** | `NULL` | 任务类型标签列表;`NULL` 时服务端回落默认四类(T-TK-14) | +| `icon` | `TEXT` | **是** | `NULL` | 项目图标 data URL(客户端裁剪 96x96 JPEG) | +| `autoArchive` | `VARCHAR(8)` | 否 | `'never'` | 自动归档档位:`3d`/`1w`/`1m`/`never`(T-ARC-02) | +| `createdAt` | `DATETIME(3)` | 否 | `now()` | — | +| `updatedAt` | `DATETIME(3)` | 否 | — | `@updatedAt` | + +索引: + +| 名称 | 字段 | 用途 | +| --- | --- | --- | +| `idx_project_owner` | `(ownerId)` | 查"我的项目" | +| `idx_project_archived` | `(archived)` | 归档分区 | + +**约束说明** +- `isPersonal` 唯一性由应用层保证(每个用户至多一个):`UNIQUE(ownerId, isPersonal)` 不可用(普通项目也是 `false`),改为在创建时查询判重。 + +### 3.2 `ProjectMember` — 项目成员 + +| 字段 | 类型 | 空 | 默认 | 说明 | +| --- | --- | --- | --- | --- | +| `id` | `INT` PK AI | 否 | — | — | +| `projectId` | `INT` | 否 | — | FK → `Project.id` `ON DELETE CASCADE` | +| `userId` | `INT` | 否 | — | FK → `User.id` `ON DELETE CASCADE` | +| `permission` | `VARCHAR(16)` | 否 | `'member'` | 预留权限分级(T-PM-03),本期恒为 `member` | +| `createdAt` | `DATETIME(3)` | 否 | `now()` | 加入时间 | + +索引: + +| 名称 | 字段 | 唯一 | 用途 | +| --- | --- | --- | --- | +| `uk_member_project_user` | `(projectId, userId)` | ✔ | 防重复加入;权限查询 | +| `idx_member_user` | `(userId)` | — | "我参与的项目" | + +> **注意**:视图记忆与上次项目**不在此表**。视图记忆存设备端(T-VW-06、T-DT-06),早期草案把它放在 `ProjectMember` 上,已在评审中否决。 + +### 3.3 `BoardColumn` — 看板列 + +**本模块新增的核心实体**,承载三件事:显示顺序、状态绑定、已完成保留选项。 + +| 字段 | 类型 | 空 | 默认 | 说明 | +| --- | --- | --- | --- | --- | +| `id` | `INT` PK AI | 否 | — | — | +| `projectId` | `INT` | 否 | — | FK → `Project.id` `ON DELETE CASCADE` | +| `name` | `VARCHAR(20)` | 否 | — | 列名,项目内唯一(T-COL-07) | +| `sortOrder` | `INT` | 否 | `0` | 显示顺序;**决定自动归位取"第一个匹配列"**(T-BD-15) | +| `boundStatus` | `VARCHAR(8)` | **是** | `NULL` | `NULL`=不绑定;或 `todo`/`doing`/`done`(T-BD-02) | +| `retention` | `VARCHAR(8)` | 否 | `'forever'` | `never`/`today`/`3d`/`week`/`month`/`forever`(T-FL-02) | +| `createdAt` | `DATETIME(3)` | 否 | `now()` | — | +| `updatedAt` | `DATETIME(3)` | 否 | — | — | + +索引: + +| 名称 | 字段 | 唯一 | 用途 | +| --- | --- | --- | --- | +| `uk_column_project_name` | `(projectId, name)` | ✔ | 列名不重名(T-COL-07) | +| `idx_column_project_sort` | `(projectId, sortOrder)` | — | 看板渲染与归位查找 | + +**默认数据**(新建项目时由应用层在事务内写入,T-COL-01、T-BD-09): + +| sortOrder | name | boundStatus | retention | +| --- | --- | --- | --- | +| 0 | `Plan` | `todo` | `forever` | +| 1 | `Doing` | `doing` | `forever` | +| 2 | `Action` | `NULL` | `forever` | +| 3 | `Done` | `done` | `forever` | + +> **为什么保留选项默认为 `forever` 而不是 `never`**:默认应"看得见",避免用户误以为数据丢失;隐藏是主动行为。 + +**约束与校验** + +| 规则 | 实现层 | +| --- | --- | +| 列数 1~10(上限待确认,T-COL-06) | 应用层:新增/删除前计数校验 | +| 至少保留 1 列(T-EX-17) | 应用层:删除前 count > 1 | +| 删列必须指定迁移目标(T-EX-16) | 应用层:接口必须带 `moveToColumnId` | +| `boundStatus` 取值合法 | 应用层白名单校验 | +| `retention` 取值合法 | 应用层白名单校验 | + +### 3.4 `Task` — 任务 / 子任务 + +| 字段 | 类型 | 空 | 默认 | 说明 | +| --- | --- | --- | --- | --- | +| `id` | `INT` PK AI | 否 | — | — | +| `projectId` | `INT` | 否 | — | FK → `Project.id` `ON DELETE CASCADE` | +| `columnId` | `INT` | 否 | — | FK → `BoardColumn.id` `ON DELETE RESTRICT`(T-BD-01) | +| `parentId` | `INT` | **是** | `NULL` | 非空 = 一级子任务;FK → `Task.id` `ON DELETE CASCADE`(T-ST-01/11) | +| `title` | `VARCHAR(200)` | 否 | — | 唯一必填(T-TK-02);应用层限 30 字(T-TK-14) | +| `description` | `TEXT` | 否 | `''` | 快速添加超长文本的余文并入此处(T-TK-14) | +| `kind` | `VARCHAR(20)` | **是** | `NULL` | 任务类型标签,取值见 `Project.taskKinds` | +| `markDate` | `DATETIME(3)` | **是** | `NULL` | 时间标记,完整日期存储、展示 `MM-dd`(T-TK-15;迁移 20261025120000) | +| `status` | `VARCHAR(8)` | 否 | `'todo'` | `todo`/`doing`/`done`(T-TK-03) | +| `doneAt` | `DATETIME(3)` | **是** | `NULL` | 完成时刻;**保留选项判定依据**(T-FL-10) | +| `assigneeId` | `INT` | **是** | `NULL` | 负责人(单人);新建默认创建者(T-TK-09) | +| `creatorId` | `INT` | 否 | — | 创建人;拷贝时为操作者(T-CP-04) | +| `extras` | `JSON` | 否 | `[]` | 扩展信息卡片 `[{label,value,url?}]`(T-TK-04/05) | +| `sortOrder` | `INT` | 否 | `0` | 列内/列表内排序 | +| `archived` | `BOOLEAN` | 否 | `false` | 归档标记;主查询一律 `archived = false`(T-ARC-01/03) | +| `archivedAt` | `DATETIME(3)` | **是** | `NULL` | 归档时刻;归档查询的过滤/排序键(T-ARC-04) | +| `archiveSource` | `VARCHAR(8)` | **是** | `NULL` | `'manual'` / `'auto'` 溯源(T-ARC-01/02) | +| `createdAt` | `DATETIME(3)` | 否 | `now()` | — | +| `updatedAt` | `DATETIME(3)` | 否 | — | — | + +外键与索引: + +| 名称 | 字段 | 类型 | 说明 | +| --- | --- | --- | --- | +| `fk_task_project` | `(projectId)` | Cascade | 删项目连带删任务 | +| `fk_task_column` | `(columnId)` | **Restrict** | 有任务的列不可直接删(见 §2 说明) | +| `fk_task_parent` | `(parentId)` | Cascade | 删父任务连带删子任务(T-EX-07) | +| `fk_task_assignee` | `(assigneeId)` | SetNull | 用户删除时清空负责人 | +| `fk_task_creator` | `(creatorId)` | Cascade | 用户删除时连带删其创建的任务* | +| `idx_task_project_column` | `(projectId, columnId)` | — | 看板按列查询 | +| `idx_task_project_status` | `(projectId, status)` | — | 列表/统计 | +| `idx_task_parent` | `(parentId)` | — | 取子任务 | +| `idx_task_done_at` | `(projectId, doneAt)` | — | 保留窗口计算 | +| `idx_task_project_archived` | `(projectId, archived, archivedAt)` | — | 已归档查询(T-ARC-04) | +| `idx_task_project_status_arch` | `(projectId, status, archived)` | — | 主列表/看板排除归档(T-ARC-03) | + +> \* `creatorId` 用 Cascade 会丢失"他人创建的任务"的历史;更稳妥的做法是 `Restrict` 并禁止删除有任务的用户。**建议改为 `Restrict`**,与既有 `User` 删除行为保持一致(本期系统无删除用户入口)。 + +**校验规则** + +| 规则 | 位置 | +| --- | --- | +| `title` trim 后非空且 ≤ 200 | 应用层 | +| `parentId` 非空时,其父任务 `parentId` 必须为空(禁止二级,T-ST-01) | 应用层 | +| 子任务 `projectId` == 父任务 `projectId`(T-EX-12) | 应用层 + 事务内校验 | +| `parentId` 不得指向自身或其后代(T-EX-15) | 应用层 | +| `status='done'` 时写 `doneAt=now()`;退出 `done` 时置 `NULL` | 应用层(领域统一裁决) | +| 归档/恢复父任务连带全部后代,同事务内更新 `archived`/`archivedAt`/`archiveSource`(T-ARC-01/05) | 应用层 | +| `autoArchive` 枚举 `3d`/`1w`/`1m`/`never`,非法 400(T-ARC-02) | 应用层 | +| `assigneeId` 必须是 `ProjectMember` 中的用户(T-AS-03) | 应用层 | + +### 3.5 `TaskMember` — 任务成员 + +| 字段 | 类型 | 空 | 默认 | 说明 | +| --- | --- | --- | --- | --- | +| `id` | `INT` PK AI | 否 | — | — | +| `taskId` | `INT` | 否 | — | FK → `Task.id` `ON DELETE CASCADE` | +| `userId` | `INT` | 否 | — | FK → `User.id` `ON DELETE CASCADE` | + +索引:`uk_task_member (taskId, userId)` 唯一。 + +### 3.6 `TaskTag` — 任务标签 + +| 字段 | 类型 | 空 | 默认 | 说明 | +| --- | --- | --- | --- | --- | +| `id` | `INT` PK AI | 否 | — | — | +| `taskId` | `INT` | 否 | — | FK → `Task.id` `ON DELETE CASCADE` | +| `name` | `VARCHAR(20)` | 否 | — | 标签名 | + +索引:`idx_tag_task (taskId)`。 + +--- + +## 4. Prisma Schema(草案) + +```prisma +model Project { + id Int @id @default(autoincrement()) + name String @db.VarChar(50) + ownerId Int + isPersonal Boolean @default(false) + archived Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + owner User @relation("ProjectOwner", fields: [ownerId], references: [id]) + members ProjectMember[] + columns BoardColumn[] + tasks Task[] + + @@index([ownerId]) + @@index([archived]) + @@map("Project") +} + +model ProjectMember { + id Int @id @default(autoincrement()) + projectId Int + userId Int + permission String @default("member") @db.VarChar(16) + createdAt DateTime @default(now()) + + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([projectId, userId]) + @@index([userId]) + @@map("ProjectMember") +} + +model BoardColumn { + id Int @id @default(autoincrement()) + projectId Int + name String @db.VarChar(20) + sortOrder Int @default(0) + boundStatus String? @db.VarChar(8) // null | todo | doing | done + retention String @default("forever") @db.VarChar(8) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + tasks Task[] + + @@unique([projectId, name]) + @@index([projectId, sortOrder]) + @@map("BoardColumn") +} + +model Task { + id Int @id @default(autoincrement()) + projectId Int + columnId Int + parentId Int? + title String @db.VarChar(200) + description String @default("") @db.Text + status String @default("todo") @db.VarChar(8) + doneAt DateTime? + assigneeId Int? + creatorId Int + extras Json @default("[]") + sortOrder Int @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + column BoardColumn @relation(fields: [columnId], references: [id], onDelete: Restrict) + parent Task? @relation("Subtask", fields: [parentId], references: [id], onDelete: Cascade) + children Task[] @relation("Subtask") + assignee User? @relation("TaskAssignee", fields: [assigneeId], references: [id], onDelete: SetNull) + creator User @relation("TaskCreator", fields: [creatorId], references: [id], onDelete: Restrict) + + members TaskMember[] + tags TaskTag[] + + @@index([projectId, columnId]) + @@index([projectId, status]) + @@index([parentId]) + @@index([projectId, doneAt]) + @@map("Task") +} + +model TaskMember { + id Int @id @default(autoincrement()) + taskId Int + userId Int + + task Task @relation(fields: [taskId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([taskId, userId]) + @@map("TaskMember") +} + +model TaskTag { + id Int @id @default(autoincrement()) + taskId Int + name String @db.VarChar(20) + + task Task @relation(fields: [taskId], references: [id], onDelete: Cascade) + + @@index([taskId]) + @@map("TaskTag") +} +``` + +`User` 需补反向关系(不改字段): + +```prisma +model User { + // …既有字段… + ownedProjects Project[] @relation("ProjectOwner") + projectMembers ProjectMember[] + assignedTasks Task[] @relation("TaskAssignee") + createdTasks Task[] @relation("TaskCreator") + taskMembers TaskMember[] +} +``` + +--- + +## 5. 关键查询 + +| 场景 | 查询 | +| --- | --- | +| 我的项目 | `SELECT p.* FROM Project p JOIN ProjectMember m ON m.projectId=p.id WHERE m.userId=? AND NOT p.archived` | +| 项目详情 + 列 | `SELECT * FROM BoardColumn WHERE projectId=? ORDER BY sortOrder` | +| 看板任务(含子任务) | 顶层:`WHERE projectId=? AND parentId IS NULL`;子任务:`WHERE parentId IN (...)` 一次取回后在内存分组(避免 N+1) | +| 权限校验 | `SELECT 1 FROM ProjectMember WHERE projectId=? AND userId=?` | +| 保留窗口过滤 | 服务端可选:`status <> 'done' OR (doneAt >= ?)`;前端另有一份同口径实现 | +| 自动归位选列 | 内存中对已加载的 `columns`(按 sortOrder)`find(c => c.boundStatus === status)` | +| 已归档查询(T-ARC-04) | `WHERE projectId=? AND archived=1 [AND kind=?] [AND columnId=?] [AND archivedAt BETWEEN ? AND ?] [AND title LIKE ?] ORDER BY archivedAt DESC, id DESC LIMIT ?+1`;多取 1 条判定 `nextCursor` | + +**防 N+1**:一次请求返回 `{ project, columns, members, tasks }`,子任务与父任务同批返回,由前端按 `parentId` 分组。 + +--- + +## 6. 迁移与初始化 + +### 6.1 迁移文件 + +`prisma/migrations/20261024xxxxxx_add_task_module/migration.sql`: + +1. 建 `Project`、`ProjectMember`、`BoardColumn`、`Task`、`TaskMember`、`TaskTag` 六张表; +2. 建外键(注意 `Task.columnId` 为 `RESTRICT`); +3. 建唯一索引与普通索引; +4. **不做数据回填**(本模块为新功能,无历史数据)。 + +后续增量迁移: + +| 迁移 | 内容 | 状态 | +| --- | --- | --- | +| `20261022120000_add_checkin_checklist` | 打卡/清单模块 | 已上线 | +| `20261025030000_task_kind` | `Project.taskKinds` / `Task.kind` | 已上线 | +| `20261025120000_task_mark_date` | `Task.markDate` | 已上线 | +| `20261025130000_project_icon` | `Project.icon` | 已上线 | +| `20261026000000_task_archive` | 任务归档三字段 + 两索引(T-ARC,DDL 见 §3.1/§3.4 与功能设计 §15) | **待实施** | + +### 6.2 个人默认项目初始化 + +```sql +-- 应用层逻辑(伪 SQL),首次访问 /api/projects 时执行 +INSERT INTO Project (name, ownerId, isPersonal, createdAt, updatedAt) +SELECT CONCAT(u.name, '的任务'), u.id, 1, NOW(3), NOW(3) +FROM User u +WHERE u.id = ? + AND NOT EXISTS (SELECT 1 FROM Project p WHERE p.ownerId = u.id AND p.isPersonal = 1); + +-- 随后为该项目的 4 个默认列插入 BoardColumn(同事务) +``` + +### 6.3 新建普通项目 + +同事务内:建 `Project`(`isPersonal=0`)→ 写 `ProjectMember(owner)` → 写 4 个默认 `BoardColumn`。**任一步失败整体回滚**,避免出现"无列项目"。 + +--- + +## 7. 容量与性能估算 + +| 项 | 估算 | 依据 | +| --- | --- | --- | +| 单项目任务 | 千级 / 年 | T-NF-05 | +| 单项目列 | ≤ 10 | T-COL-06 | +| `Task` 行宽 | ~400 B + JSON | 含 `extras` | +| 单项目年增量 | ≈ 400 KB | 1k 任务 × 400B | +| 索引开销 | 约表大小 30% | 4 个二级索引 | + +**无需分表**;按 `projectId` 的查询均可走索引。 + +--- + +## 8. 与既有模块的关系 + +| 项 | 说明 | +| --- | --- | +| `User` 表 | 仅补反向关系,**不改结构**,不影响打卡/宏观/监控模块 | +| 打卡模块(`Habit`/`CheckIn`) | 完全独立,同为"按用户隔离";任务模块改为"按项目隔离" | +| 迁移顺序 | 在 `20261022120000_add_checkin_checklist` 之后追加,互不依赖 | +| 时区 | 沿用 Asia/Shanghai 应用层口径;`doneAt` 用 `DATETIME(3)` 存 UTC | + +--- + +## 9. 未决事项 + +| # | 事项 | 当前处理 | +| --- | --- | --- | +| 1 | 列数上限是否 10 | 按 10 设计(T-COL-06 标注"待确认"),若定其他值只改常量 | +| 2 | `Task.creatorId` 外键行为 | 草案建议 `Restrict`(见 §3.4 注),需与"用户删除策略"一并确认 | +| 3 | 孤儿项目(无成员)的维护 | 本期不处理(T-PJ-08),由后台流程清理;数据库层保留数据 | +| 4 | `extras` 条目数量上限 | 建议 ≤ 20,未在需求中约束,设计预留 | diff --git a/docs/V0.2/任务模块/任务模块界面设计.html b/docs/V0.2/任务模块/任务模块界面设计.html new file mode 100644 index 0000000..5f4d15d --- /dev/null +++ b/docs/V0.2/任务模块/任务模块界面设计.html @@ -0,0 +1,1432 @@ + + + + + +iBoard V0.2 界面设计 · 任务模块 + + + +
+ +
+

iBoard V0.2 界面设计 · 任务模块

+

轻量任务 / 项目管理 —— 列表与看板双视图 · 列-状态绑定 · 按列保留选项 · 一级子任务

+
+ 版本 V0.2视口 420 × 820移动端优先 + 风格沿用《界面设计.html》需求编号 T-* +
+
+ +

1 设计规范 沿用主文档 token,仅新增任务模块语义色

+

任务模块不引入新主色,全部复用 V0.2 既有色板;只在「任务状态」「列绑定」两处新增语义映射。

+
+
+

主色 / 语义色

+
+
Primary#1677ff
+
Success#00b578
+
Warn#ffc53d
+
Danger#ff3141
+
Idle#c9ced6
+
+
+
+

任务状态映射

+
未开始 todo灰点 #c9ced6,与「空闲/未启用」同色
+
执行中 doing主色点 #1677ff,与选中态一致
+
完成 done成功绿 #00b578,勾选态一致
+
+
+

尺寸 / 间距

+
+ 页面边距12px + 卡片圆角8~10px + 看板列宽186px(固定,横向滚动) + 列表行高≈ 58px(含子任务元信息) + 子任务缩进28px + 层级线 1px + 点击热区≥ 44 × 44px + 标题字号14px / 600(列表)· 12.5px / 600(卡片) + 负责人12px / 600(突出) + 任务成员10.5px / 400 · #999(弱化) +
+
+
+

布局约定

+
TabBar5 项:打卡 / 任务 / 宏观 / 监控 / 我(任务在打卡之后,T-PM-06)
+
头部项目页用 navbar-sub(44px,左返回 + 标题 + 右侧操作)
+
视图切换列表 / 看板 用 seg 分段,切换不刷新
+
看板列横向滚动,列体纵向滚动,两轴分离
+
+
+ +

2 任务首屏 最近项目条(可排序)+ 当前项目任务管理 · 更多进项目列表

+

首屏上部为最近使用的 3 个项目卡片 + 「更多项目」入口(T-VW-08),卡片可长按拖动排序(T-VW-10,设备端记忆);下部直接是当前选中项目的任务管理视图(T-VW-09,默认上次访问的项目),不必先进项目列表。完整项目列表从「更多项目」进入。

+ +
+
+
+ +
+ +
+
+
张
+
张三的任务
+
12 个任务
+
⠿
+
+
+
APP
+
APP 改版
+
28 个任务
+
⠿
+
+
+
二
+
APP 二期
+
9 个任务
+
⠿
+
+
+
▤
+ 更多项目 +
+
+
长按卡片可调整顺序(本机记忆)
+ + + +
+
列表
看板
+
过滤
+
+
+
+
+
+
登录接口返回 500
+
+ 执行中 + Doing + 张三 + + 李四、王五 +
+
+
›
+
+
+
+
+
首页改版视觉走查
+
+ 执行中 + Doing + 李四 +
+
+
›
+
+
+
+
+
需求评审
+
+ 完成 + Done + 王五 +
+
+
›
+
+
+
+
+
打卡
+
任务
+
宏观
+
监控
+
我
+
+
+
+ 任务首屏(T-VW-08/09) +

上:最近 3 个项目卡片,当前项目主色描边高亮;长按「⠿」拖动排序(T-VW-10,设备端)。下:当前项目的任务管理,默认上次访问的项目。项目页头部仅保留「项目管理」+「+ 新增」两个入口(T-PJ-11)。

+
+
+ +
+
+ + +
+
+
张
+
+
张三的任务 个人
+
12 个任务 · 3 个进行中
+
+
›
+
+ +
协作项目(3)
+
+
APP
+
+
APP 改版
+
28 个任务 · 4 人 · 6 个子任务
+
+
›
+
+
+
二期
+
+
APP 二期
+
9 个任务 · 2 人
+
+
›
+
+
+
运
+
+
运维排期
+
5 个任务 · 1 人
+
+
›
+
+ +
已归档(1)
+
+
旧
+
+
2025 遗留
+
已归档 · 14 个任务
+
+
›
+
+
+
+
打卡
+
任务
+
宏观
+
监控
+
我
+
+
+
+ 我的项目列表(从「更多项目」进入) +

完整项目列表:个人默认项目带「个人」徽标、无删除入口;归档项目降透明分组。选择项目后返回首屏并切换当前项目(T-VW-09)。

+
+
+
+ +

3 列表视图 Todoist 风格 · 一级子任务缩进折叠

+

默认视图(T-VW-05)。子任务缩进 28px 并带层级线(T-ST-04),可折叠(T-ST-05);已完成任务按所属列的保留选项隐藏(T-FL-11)。

+ +
+
+
+ + +
+
+
列表
看板
+
过滤
+
+ +
Doing 2
+ +
+
+
+
登录接口返回 500
+
+ 执行中 + Doing + 张三 + + 李四、王五 +
+
+
›
+
+ +
+
+ 复现环境搭建 + 完成 +
+
+
+ 补单元测试 + 执行中 +
+
收起 2 个子任务 ⌃
+ +
+
+
+
首页改版视觉走查
+
+ 执行中 + Doing + 李四 +
+
+
›
+
+
展开 1 个子任务 ⌄
+ +
Plan 1
+
+
+
+
接入埋点上报
+
+ 未开始 + Plan + 未指派 +
+
+
›
+
+ +
Done 1
+
+
+
+
需求评审
+
+ 完成 + Done + 王五 +
+
+
›
+
+
+
+
+
打卡
+
任务
+
宏观
+
监控
+
我
+
+
+
+ 列表视图 +

按看板列分组显示(列名可关闭)。负责人 12px/600 突出,任务成员 10.5px 弱化并置于下方(T-AS-01/02)。子任务带独立状态小标记(T-ST-09),可与父任务不一致。

+
+
+ +
+
+ + +
+
+
列表
看板
+
过滤
+
+ +
Done 3
+
+
+
+
接口联调
+
完成于 今天Done张三
+
+
›
+
+ +
+
+
+
旧任务 · 超出保留期
+
完成于 昨天Done
+
+
›
+
+
+
+
+
修复打包脚本
+
完成于 今天Done李四
+
+
›
+
+ +
已隐藏 1 条超出保留期的任务
+ +
+ 退场动画(T-EX-18)
+ 跨日 / 跨周 / 跨月边界到点后,超出所属列保留选项(如「当天」)的已完成任务:透明度与高度在 240ms 内收缩至 0 → 移除节点 → 其余条目平滑重排。不做整页刷新。 +
+
+
+
打卡
+
任务
+
宏观
+
监控
+
我
+
+
+
+ 保留选项 · 当场消失 +

该列保留选项为「当天」,昨日完成的任务在跨日后进入退场态并移除,其余任务重排(T-FL-05/09、T-EX-18)。

+
+
+
+ +

4 看板视图 列-状态绑定 · 卡片内嵌子任务 · 长按拖拽

+

列头显示列名、数量与绑定标记(↔ 完成 表示该列绑定「完成」状态,未绑定 表示不参与状态联动)。

+ +
+
+
+ + +
+
+
列表
看板
+
过滤
+
+ +
+ +
+
+ Plan2 + ↔ 未开始 +
+
+
+
接入埋点上报
+
未指派
+
+
+
整理 0.3 需求清单
+
张三+ 王五
+
收集各方诉求
+
+
+ 添加任务
+
+
+ + +
+
+ Doing2 + ↔ 执行中 +
+
+
+
登录接口返回 500
+
张三+ 李四、王五
+
+
+
首页改版视觉走查
+
李四
+
确认色板与字号
+
产出走查清单
+
+1 个子任务
+
+
+ 添加任务
+
+
+ + +
+
+ Action1 + 未绑定 +
+
+
+
待产品确认的 3 个问题
+
王五
+
+
+ 添加任务
+
+
+ + +
+
+ Done1 + ↔ 完成 +
+
+
+
需求评审
+
王五
+
会前材料准备
+
+
+ 添加任务
+
+
+
+ +
拖入 Doing,状态将改为「执行中」
+
+
+
打卡
+
任务
+
宏观
+
监控
+
我
+
+
+
+ 看板视图 + 拖拽中 +

卡片轻量化(T-UI-02):标题 + 状态点 + 负责人(小字)+ 成员(更弱)+ 子任务摘要。拖拽中卡片半透明抬起,目标列蓝色虚线高亮。子任务内嵌展示并可勾选(T-ST-06/07),带自身状态点(T-ST-09)。

+
+
+ +
+
+ + +
+
+
列表
看板
+
过滤
+
+ +
+
+
Plan1↔ 未开始
+
+
+
接入埋点上报
+
未指派
+
+
+
+ +
+
Doing1↔ 执行中
+
+ +
首页改版视觉走查
+
+
登录接口返回 500
+
张三
+
+ 松手 → 成为子任务 +
+
+
+
+ +
+
Action0未绑定
+
+ 添加任务
+
+ +
+
Done1↔ 完成
+
+
+
需求评审
+
王五
+
+
+
+
+ +
仅支持一级子任务
+
+
+
打卡
+
任务
+
宏观
+
监控
+
我
+
+
+
+ 拖拽降为子任务(T-ST-12) +

长按卡片拖到另一张卡片上松手即成为其一级子任务。目标本身是子任务、或被拖任务已有子任务,均拒绝并 Toast「仅支持一级子任务」(T-ST-13/14)。

+
+
+
+ +

5 列设置 列管理 + 状态绑定 + 保留选项

+

本模块的配置中枢:每列一行,包含重命名、状态绑定(T-BD-02)与保留选项(T-FL-02)。列可拖动排序(T-COL-05)、增删(T-COL-02/04),数量 1~10。

+ +
+
+
+ + +
+
+ 拖动左侧手柄调整列顺序;顺序决定「改状态时自动归位」的目标列(取第一个匹配列)。 +
+ +
+ ≡ + Plan +
+
未开始 ▾
+
+
+ +
+ ≡ + Doing +
+
执行中 ▾
+
+
+ +
+ ≡ + Action +
+
不绑定状态 ▾
+
+
+ +
+ ≡ + Done +
+
完成 ▾
+
+
+ +
+
+ 新增列
+
删除列
+
+ +
已完成任务保留
+ +
+ Plan +
永久显示 ▾
+
+
+ Doing +
本月 ▾
+
+
+ Action +
3 天 ▾
+
+
+ Done +
当天 ▾
+
+ +
+ 保留选项:永不显示 / 当天 / 3 天 / 本周 / 本月 / 永久显示。
+ 仅影响已完成任务;未完成任务始终展示。列表与看板按同一规则过滤。 +
+ +
+
保存
+
取消
+
+
+
+
打卡
+
任务
+
宏观
+
监控
+
我
+
+
+
+ 列设置 +

上半区是「列 → 状态」绑定,下半区是「列 → 保留选项」。Action 显示为「不绑定状态」灰态;保存后看板与列表即时生效,不批量改写已有任务(T-BD-11/12)。

+
+
+ +
+
+ + +
+
+
列表
看板
+
过滤
+
+
+
+
Plan0↔ 未开始
+
+ 添加任务
+
+
+
Doing0↔ 执行中
+
+ 添加任务
+
+
+
Action1未绑定
+
+
+
待产品确认的 3 个问题
+
王五+ 李四
+
+
+
+
+
Done0↔ 完成
+
+ 添加任务
+
+
+
状态已改为「完成」,Action 未绑定完成,任务保留在原列
+
+
+
打卡
+
任务
+
宏观
+
监控
+
我
+
+
+
+ 自动归位 · 无匹配列 +

任务在未绑定的 Action 中被勾成「完成」。若其他列绑定了「完成」→ 自动移入第一个匹配列;若全部列都未绑定「完成」→ 不移动,保留在原列(T-BD-14/16)。此屏即后者。

+
+
+
+ +

6 任务详情 字段 + 扩展信息卡片(默认收起)

+

底部弹层。源码等扩展信息放在可折叠卡片内,默认收起、全部非必填(T-TK-04~07),不占用主字段空间。

+ +
+
+
+ + +
+
+
+
登录接口返回 500
+
APP 改版 · Doing · 执行中 · 创建于 10-22
+ +
+ +
登录接口返回 500
+
+ +
+ +
补充说明…
+
+ +
+ +
未开始
执行中
完成
+
+ +
+ +
+
张
+ 张三 + 更换 +
+
+ +
+ +
+
李
王
+ 李四、王五 + 编辑 +
+
+ + +
+ ▸扩展信息 + 2 + 默认收起 · 全部非必填 +
+ +
+
保存
+
+
+
+
+
打卡
+
任务
+
宏观
+
监控
+
我
+
+
+
+ 详情(扩展信息收起) +

负责人默认填入创建者本人,可清空为「未指派」(T-TK-09/10)。扩展信息卡片默认收起,右侧标出条目数。弹层底部预留 安全区 + 20px(T-UI-11):删除按钮不被手势条遮挡,小屏下弹层内部滚动、底部按钮始终完整可见。

+
+
+ +
+
+ + +
+
+
+
登录接口返回 500
+
APP 改版 · Doing · 执行中
+ +
+ +
+
张
+ 张三 + 更换 +
+
+ +
+ ▾扩展信息 + 2 + 可自由增删条目 +
+ +
+
仓库
+
gitlab/app-web
+
+
+
分支
+
fix/login-500
+
+
+
相关链接
+
https://…/issues/128
+
+ +
+ + 添加一条 +
+ +
子任务 2
+
+ + 复现环境搭建 + 完成 +
+
+ + 补单元测试 + 执行中 +
+
+ + + 添加子任务 +
+ +
保存
+
删除任务(将一并删除 2 条子任务)
+
+
+
+
打卡
+
任务
+
宏观
+
监控
+
我
+
+
+
+ 详情(扩展信息展开) +

扩展条目为任意名称 + 值,不限固定字段(T-TK-05),值可为文本或链接。子任务在此可增删改,状态与父任务互相独立(T-ST-03)。删除任务会提示级联条数(T-EX-07)。

+
+
+
+ +

7 项目管理 · 成员与跨项目拷贝 统一入口:成员 / 列设置 / 项目信息

+

项目页头部的「项目管理」进入统一管理页(T-PJ-11),含成员、列设置、项目信息三个分区;负责人与任务成员只能从当前项目成员中选择(T-AS-03/04);跨项目操作是拷贝(T-CP-01)。

+ +
+
+
+ + +
+
+ 项目内所有成员权限均等、全读写(T-PM-02);以下配置对全体成员即时生效。 +
+ +
成员(4)
+
+
张
+ 张三 创建人 + 管理 › +
+ +
看板列
+
+ Plan · Doing · Action · Done + 列设置 › +
+ +
项目信息
+
+ 项目名称 + APP 改版 +
+
+ 归档项目 + 归档 +
+
+ 删除项目 + 删除 +
+ +
+ 个人默认项目不出现「删除项目」;退出项目仅创建人可见(T-PJ-07)。删除与退出均有二次确认。 +
+
+
+
打卡
+
任务
+
宏观
+
监控
+
我
+
+
+
+ 项目管理(T-PJ-11/12) +

三分区:成员(增删)、列设置(进入第 5 节的列设置页)、项目信息(改名 / 归档 / 删除 / 退出)。项目页头部不再散置成员与列设置入口。

+
+
+ +
+
+ + +
+
+ 项目内所有成员权限均等、全读写;负责人与成员只是信息标记,不影响权限(T-AS-07、T-PM-02)。 +
+ +
+
张
+ 张三 创建人 + 退出 +
+
+
李
+ 李四 + 移除 +
+
+
王
+ 王五 + 移除 +
+
+
赵
+ 赵六 已退出 + — +
+ +
+ 成员被移出项目后,其担任的负责人 / 成员标记保留历史并显示「已退出」灰标,不清空、不报错(T-EX-06)。 +
+
+
+
打卡
+
任务
+
宏观
+
监控
+
我
+
+
+
+ 项目成员 +

创建人可主动退出(T-PJ-07);成员清空后项目静默保留、前端不报错(T-PJ-08、T-EX-11)。

+
+
+ +
+
+ + +
+
+
+
跨项目拷贝
+
将「登录接口返回 500」复制到另一个项目;源任务保持不变
+ +
+ +
APP 二期 ▾
+
+ +
+ +
+ 标题、描述、状态、看板列、标签
+ 负责人与任务成员(仅保留目标项目成员)
+ 2 条子任务 · 2 条扩展信息 +
+
+ +
+ +
+ 王五 不是「APP 二期」的成员,拷贝后将不保留其任务成员标记。 +
+
+ +
确认拷贝
+
取消
+
+
+
+
打卡
+
任务
+
宏观
+
监控
+
我
+
+
+
+ 拷贝确认 +

明确列出复制内容与被丢弃的人员(T-EX-08),避免静默丢数据。拷贝后新旧任务完全独立(T-CP-05)。

+
+
+
+ +

8 交互与状态 长按拖拽 · 三态 · 边界

+ +

8.1 拖拽与落点

+ + + + + + + + +
操作落点结果
长按卡片 ≥ 300ms—卡片抬起(缩 0.97 + 倾斜 1°),进入拖拽态,锁定滚动
拖到另一列空白列体换列;目标列绑定状态则状态跟随,未绑定则保留原状态(T-BD-07)
拖到另一张卡片上顶层任务卡降为该任务的一级子任务;原列与状态保持不变(T-ST-15)
拖到子任务 / 自身 / 后代非法落点拒绝并 Toast,卡片回弹(T-ST-13、T-EX-15)
被拖任务有子任务任何卡片拒绝,避免二级嵌套(T-ST-14)
无需拖拽时卡片「⋯」「移动到列 / 移动到子任务」等价入口(T-UI-07)
+ +

8.2 三态与反馈

+
+
+
+ + +
+
列表
看板
+
+
+
+
+
+
+
打卡
+
任务
+
宏观
+
监控
+
我
+
+
+
加载态(T-UI-09)

骨架屏,避免白屏与布局跳动。

+
+ +
+
+ + +
+
列表
看板
+
+
✓
+

这个项目还没有任务

+
创建第一个任务
+
+
+
+
打卡
+
任务
+
宏观
+
监控
+
我
+
+
+
空态(T-UI-08)

带明确 CTA;某列因保留选项隐藏全部已完成任务时,展示该列空态而非报错(T-EX-10)。

+
+ +
+
+ + +
+
列表
看板
+
+
!
+

数据加载失败,请检查网络

+
重试
+
+
+
+
打卡
+
任务
+
宏观
+
监控
+
我
+
+
+
异常态(T-UI-10)

乐观更新失败时回滚并 Toast,可重试(T-EX-02)。

+
+
+ +

9 需求点对照 界面稿 ↔ 需求编号

+ + + + + + + + + + + + +
界面对应需求点
2 任务首屏T-VW-08/09/10、T-VW-03~07、T-PM-06/07、T-PJ-11(头部入口)
2′ 我的项目列表T-PJ-01~10、T-UI-08
3 列表视图T-VW-01/02、T-ST-04/05/09、T-AS-01/02、T-FL-11、T-TK-12
3′ 保留退场T-FL-05/09、T-EX-18
4 看板视图T-BD-01~16、T-UI-02/07、T-ST-06/07/09、T-ST-12~15
5 列设置T-COL-01~10、T-BD-02/09/10、T-FL-01~15
5′ 自动归位T-BD-14/16、T-EX-04/05
6 任务详情T-TK-01~13、T-ST-03、T-AS-05、T-EX-07、T-UI-11(底部安全距离)
7 项目管理 / 成员 / 拷贝T-PJ-11/12、T-AS-03/04/06/07、T-PM-01~05、T-CP-01~09、T-EX-06/08
8 三态T-UI-08/09/10、T-EX-02/10
+ +

10 适配与可达性

+ + + + + + + + + + +
项约定
视口设计稿 420×820;375~430px 内元素不换行、不裁切;底部 TabBar 5 项每项 ≥ 44px 可点宽度
安全区TabBar 使用 antd-mobile safeArea,页面底部留出 tabbar-h + safe-bottom
看板宽度列宽固定 186px,超出横向滚动;列头吸顶,列体独立纵向滚动
触控长按 300ms 进入拖拽;点击热区 ≥ 44×44;拖拽中禁用页面滚动
动效退场 240ms、拖动抬起 150ms、Toast 2s 自动消失;均 pointer-events:none 不阻塞后续操作
大字号任务标题超长换行不截断(卡片内最多 2 行省略);列名 ≤ 12 字
深色模式V0.2 沿用现有浅色主题,暂不适配
数量上限看板列 1~10(上限待确认);扩展信息条目建议 ≤ 20
+ +

本文档为静态设计稿,不含脚本逻辑;实现对应 src/pages/task/*.jsx、src/components/task/*.jsx 与样式段(src/styles/globals.css 的 .task-*)。接口与数据口径见《任务模块设计文档-框架设计.md》《任务模块功能设计.md》与《任务模块数据库设计文档.md》。

+ +
+ + diff --git a/docs/V0.2/任务模块/任务模块设计文档-框架设计.md b/docs/V0.2/任务模块/任务模块设计文档-框架设计.md new file mode 100644 index 0000000..543e3aa --- /dev/null +++ b/docs/V0.2/任务模块/任务模块设计文档-框架设计.md @@ -0,0 +1,451 @@ +# iBoard V0.2 任务模块 设计文档 — 框架设计 + +| 项 | 内容 | +| --- | --- | +| 版本 | V0.2(对应分支 `dev-0.2`,应用版本 `0.2.0`) | +| 文档状态 | **设计基线**(待评审;不含实现代码) | +| 上游文档 | `任务模块需求文档.md`(需求编号 `T-*` 原文见该文档,本文按编号逐条对应) | +| 配套文档 | `任务模块功能设计.md`(功能设计,需求点一一对应)、`任务模块数据库设计文档.md`、`任务模块界面设计.html` | +| 技术栈 | Tauri 2 + React 18 + antd-mobile + React Router v6 + Express 4 + Prisma / MariaDB | +| 设计基线 | 移动端优先,视口 420×820,375~430px 自适应;时区统一 `Asia/Shanghai` | + +> **编号约定**:本文多处出现需求编号(如 `T-BD-14`),其完整描述与验收要点以《任务模块需求文档》为准。每个模块的「需求点 → 设计落点」对照见《任务模块功能设计》。 + +--- + +## 1. 总体设计 + +### 1.1 目标与范围 + +在既有 iBoard 之上新增**任务模块**,并把它作为**顶级 Tab 插入「打卡」之后**(顺序:打卡 → **任务** → 宏观 → 监控 → 我,默认首页仍为打卡,T-PM-06)。 + +模块解决的问题与核心隐喻: + +| 视图 / 概念 | 解决的问题 | 核心隐喻 | +| --- | --- | --- | +| **列表视图** | 快速记录、逐条推进 | Todoist 式条目 + 一级子任务缩进 | +| **看板视图** | 多人分工下的阶段流转 | 列(阶段)× 卡片(任务),长按拖拽换列 | +| **项目** | 数据与权限的隔离单位 | 项目即"工作区",成员可见即可读写 | +| **列-状态绑定** | 让"拖动列"与"改状态"两个动作不必重复做 | 列是**空间**,状态是**属性**,绑定是二者之间的**可选桥** | +| **列保留选项** | 已完成任务不淹没在列表里 | 每列自己的"保质期" | + +**设计的关键取舍**:把「看板列」与「任务状态」设计成**两个独立维度**,再用**项目级、逐列的绑定配置**把它们关联起来。这样既支持"拖动即改状态"的一体化体验,也支持"这一列只是分类、不进状态机"的轻量用法——这正是需求中 `Action` 默认不绑定状态的意图。 + +### 1.2 架构总览 + +``` +┌──────────────────── 前端 (React + antd-mobile,打包进 Tauri / OTA) ────────────────────┐ +│ App.jsx 路由 │ +│ └ /task ──► TaskPage (首屏: 上=RecentProjects 条, 下=当前项目 ProjectView) │ +│ /task/list ──► ProjectList (完整项目列表, 从「更多项目」进入) │ +│ /task/:pid ──► TaskPage (打开指定项目, 供深链) │ +│ ProjectView ──┬── ListView (列表 + 子任务缩进折叠) │ +│ ├── BoardView (看板 + 长按拖拽) │ +│ └── ProjectManage (项目管理: 成员 / 列设置 / 项目信息) │ +│ 组件: TaskCard / SubtaskRow / StatusDot / ExtrasCard / UserPicker / RecentProjects │ +│ 状态: TaskStore (项目内单例) / ReorderableList / useLongPressDrag │ +│ 工具: src/pages/task/date.js (与后端同口径的自然日边界) │ +│ src/pages/task/prefs.js (设备端: 视图记忆 / 上次项目 / 最近项目手动排序) │ +└──────────────────────────────┬─────────────────────────────────────────────────────────┘ + │ HTTPS + JWT (Authorization: Bearer) +┌──────────────────────────────▼─────────────────────────────────────────────────────────┐ +│ Express │ +│ /api/projects/* ─► routes/projects.js ─┐ │ +│ /api/tasks/* ─► routes/tasks.js ─┤─► middleware.authRequired │ +│ /api/columns/* ─► routes/columns.js ─┤─► requireProjectMember (项目成员校验) │ +│ └─► task-domain/ ← 【领域逻辑唯一来源】 │ +│ resolveStatus() 列/状态联动裁决 │ +│ resolveColumn() 自动归位 │ +│ retentionWindow() 保留窗口 │ +│ copyTask() 跨项目拷贝 │ +└──────────────────────────────┬─────────────────────────────────────────────────────────┘ + ▼ + MariaDB: Project / ProjectMember / BoardColumn / Task / TaskMember / TaskTag +``` + +### 1.3 分层与职责 + +| 层 | 位置 | 职责 | +| --- | --- | --- | +| 页面 | `src/pages/task/*.jsx` | 视图编排、本地状态、乐观更新与回滚 | +| 组件 | `src/components/task/*.jsx` | 纯展示 + 局部交互(卡片、列、子任务行、状态点、扩展卡片) | +| 设备端偏好 | `src/pages/task/prefs.js` | `viewMode` / `lastProjectId`(**仅设备端**,T-VW-06、T-VW-07) | +| 日期工具 | `src/pages/task/date.js` | 自然日边界计算,**与后端 `task-domain/date.js` 同口径,改一处必须同步另一处** | +| API 封装 | `src/api/task.js` | 统一走既有 `api()`(自带 token / 错误解析) | +| 路由 | `server/routes/tasks.js`、`projects.js`、`columns.js` | 参数校验、鉴权、组装展示态 | +| 领域逻辑 | `server/task-domain/*.js` | **列-状态裁决、自动归位、保留窗口、拷贝**(唯一口径) | +| 持久化 | Prisma Client | 表操作 | + +> **与打卡模块的区别**:打卡的周期口径只有 `period.js` 一个文件;任务模块的领域规则(三条)彼此有依赖(归位依赖列序、保留依赖完成时间与列配置),统一收敛到 `task-domain/` 目录,**前后端不得各自实现一遍裁决逻辑**——前端只做乐观更新,最终以服务端返回为准。 + +### 1.4 目录结构(本次新增/修改) + +``` +prisma/ + schema.prisma # +6 个 model,User 加反向关系 + migrations/20261024xxxxxx_add_task_module/ # 新建表 + 外键 + 索引 +server/ + task-domain/ + date.js # 自然日边界(今天/本周/本月) + resolveStatus.js # 列→状态、状态→列、自动归位 + retention.js # 列保留窗口 → 可见性 + copyTask.js # 跨项目深拷贝 + routes/ + projects.js # /api/projects/* + columns.js # /api/columns/* + tasks.js # /api/tasks/* + index.js # 挂载三个路由 +src/ + api/task.js # projectApi / columnApi / taskApi + pages/task/ + date.js # 与后端同口径 + prefs.js # 设备端偏好(视图记忆/上次项目/最近项目手动排序) + TaskPage.jsx # 首屏容器: RecentProjects 条 + 当前项目 ProjectView + ProjectList.jsx # 完整项目列表(/task/list, 从「更多项目」进入) + ProjectView.jsx # 列表/看板容器 + ListView.jsx # 列表视图 + BoardView.jsx # 看板视图 + ColumnSettings.jsx # 列管理 + 绑定 + 保留选项(由项目管理进入) + TaskDetail.jsx # 任务详情(含扩展卡片 / 子任务;底部安全距离) + ProjectMembers.jsx # 成员管理(由项目管理进入) + ProjectManage.jsx # 项目管理: 成员 / 列设置 / 项目信息 三区 + components/task/ + TaskCard.jsx SubtaskRow.jsx StatusDot.jsx + ExtrasCard.jsx UserPicker.jsx useLongPressDrag.js + RecentProjects.jsx # 首屏最近项目条(卡片可长按排序) + components/BottomNav.jsx # Tab 增「任务」,位于打卡之后 + components/TabIcon.jsx # 增 task 图标 + App.jsx # +/task、/task/:pid + styles/globals.css # .task-* 样式段 +docs/V0.2/任务模块/ # 需求 / 框架 / 功能 / 数据库 / 界面 +``` + +### 1.5 数据流总览 + +``` +用户操作 ──► 前端乐观更新(本地 store 立即变) + │ + ▼ + PUT /api/tasks/:id { columnId?, status?, done? } + │ + ▼ + requireProjectMember(非成员 403) + │ + ▼ + resolveStatus() ── 读项目 columnBindings(列序 + 绑定) + │ ① 目标列绑定状态 ⇒ status = boundStatus + │ ② 目标列不绑定 ⇒ 保留原 status + │ ③ 状态变了且当前列不绑定该状态 ⇒ 自动归位(列序首个匹配) + │ ④ 无匹配列 ⇒ 列不动 + ▼ + 写库(status + columnId 同时落库) + │ + ▼ + 返回最新任务 ──► 前端以响应覆盖本地乐观值(不一致则纠正) +``` + +--- + +## 2. 领域规则设计(重点) + +> 本模块所有"难"的地方都集中在**列与状态的双向联动**上。下面把规则收敛为一组**无歧义的判定函数**,前后端都以此为准。 + +### 2.1 两个正交维度的定义 + +| 维度 | 字段 | 取值 | 谁决定 | +| --- | --- | --- | --- | +| **空间** | `Task.columnId` | 指向项目内某个 `BoardColumn` | 用户拖拽 / 自动归位 | +| **属性** | `Task.status` | `todo`(未开始)/ `doing`(执行中)/ `done`(完成) | 用户勾选 / 列绑定联动 | + +**二者独立落库**,不做互相推导(见《数据库设计文档》§2)。原因: + +1. 未绑定的列(如默认 `Action`)要求二者**完全正交**,任何推导都会丢失信息; +2. 同一状态可被多列绑定(T-BD-03),状态无法反推唯一列; +3. 保留选项依赖"完成时间"而非列,推导会引入隐式状态。 + +### 2.2 三种联动场景与判定函数 + +设项目列列表为 `C = [c₁, c₂, …, cₙ]`(按 `sortOrder` 升序),每列 `c.boundStatus ∈ {todo, doing, done, null}`。 + +**场景 A · 拖拽换列(`T-BD-04/07/08`)** + +``` +function onColumnChange(task, targetColumnId): + c = columnById(targetColumnId) + if c.boundStatus != null: + task.status = c.boundStatus # 列决定状态 + else: + # 目标列不绑定 ⇒ 保留原状态(T-BD-07、T-EX-05) + pass + task.columnId = targetColumnId +``` + +> **注意**:这里**不存在「从某列拖出即回退状态」的规则**。是否改状态只看**目标列**。这是评审中明确修正过的口径(原设计曾写"拖出回退为未开始",已废弃)。 + +**场景 B · 改状态(勾选/选择器,`T-BD-14/15/16`)** + +``` +function onStatusChange(task, newStatus): + task.status = newStatus + # 当前列是否已经"匹配"这个状态? + if task.column.boundStatus == newStatus: + return # 已匹配,列不动 + # 不匹配 ⇒ 自动归位,按列序取第一个绑定 newStatus 的列(T-BD-15) + target = first(c in C where c.boundStatus == newStatus) + if target != null: + task.columnId = target.id + else: + # 无匹配列 ⇒ 不移动,保留原列(T-BD-16) + pass +``` + +**场景 C · 子任务(`T-BD-13`)** + +``` +if task.parentId != null: + # 子任务完全跳过上述两种联动 + persist(status, columnId) as-is +``` + +### 2.3 判定矩阵(可转写为单测) + +以默认配置 `plan→todo, doing→doing, action→null, done→done` 为例: + +| # | 初始(列 / 状态) | 操作 | 结果(列 / 状态) | 覆盖需求 | +| --- | --- | --- | --- | --- | +| 1 | plan / todo | 拖到 doing | doing / doing | T-BD-04 | +| 2 | plan / todo | 拖到 action(不绑定) | action / **todo**(保留) | T-BD-07、T-BD-08 | +| 3 | action / todo | 改为 done | **done** / done(自动归位) | T-BD-14、T-BD-15 | +| 4 | action / todo | 改为 doing | doing / doing | T-BD-14 | +| 5 | done / done | 拖到 action | action / **done**(保留) | T-BD-07、T-EX-05 | +| 6 | done / done | 改为 doing | doing / doing | T-BD-14 | +| 7 | plan / todo | 改为 todo | plan / todo(已匹配,不动) | T-BD-14 | +| 8 | 全部列不绑定 done,任务在 action | 改为 done | action / done(**无匹配不移动**) | T-BD-16 | +| 9 | 父任务 done,子任务在 action | 改父任务状态 | 子任务完全不受影响 | T-BD-13 | +| 10 | 两列都绑定 done(done、action) | 改为 done | 列序靠前者(done) | T-BD-06、T-BD-15 | + +> 第 8 行是评审中特别确认的:**没有「已完成统一归到 Done 列」的强制规则**,那只是默认配置下的自然效果。用户取消全部绑定后,任务就停在原列。 + +### 2.4 列配置变更的语义(`T-BD-11`、`T-COL-08`) + +**核心原则:配置变更只影响后续操作,不批量改写历史数据。** + +| 变更 | 处理 | +| --- | --- | +| 列 A 由「绑定 done」改为「不绑定」 | 已在 A 中且状态为 done 的任务**保持不动**(列与状态都保留),见 T-EX-03 | +| 列 A 绑定状态由 doing 改为 done | 已在 A 中的任务状态**不回写**;后续拖入 A 的任务才按 done 处理(T-BD-11) | +| 删除列 A | 二次确认 → 列内任务迁移到指定列(默认相邻列)→ 迁移后**按目标列重新裁决状态**;A 的绑定解除(T-COL-08、T-EX-16) | +| 新增列 | 取默认值 `retention=forever`、`boundStatus=null`,**不继承相邻列**(B-18) | + +> 为什么删除列要"重新裁决"而改绑定不裁决:删除是**数据搬家**(任务必须有个列),搬家后必须与目标列的语义自洽;而改绑定只是改了"未来的规则",不该动已有数据。 + +### 2.5 空归属与一致性保障 + +| 风险 | 保障 | +| --- | --- | +| 删除列后任务 `columnId` 悬空 | 迁移在同事务内完成;`Task.columnId` 为 `NOT NULL`,外键 `ON DELETE RESTRICT` 从库层面阻止误删 | +| 列被删后 `boundStatus` 残留 | `BoardColumn` 是实体,列删则绑定自然消失,无残留(优于早期 JSON 方案) | +| 并发拖拽同一任务 | 服务端以「读-判定-写」在同一事务内完成;前端防抖 + 版本号(可选) | +| 状态改了但归位列不存在 | 判定函数显式处理「无匹配列」分支(T-BD-16),不抛异常 | + +--- + +## 3. 重点流程设计 + +### 3.1 看板拖拽(长按 → 跨列 → 落到子任务) + +这是本模块交互最复杂的一条链路,同时涉及**换列**与**降级为子任务**两种语义。 + +``` +长按 300ms ─► 卡片抬起(缩小 + 浮层跟随手指) + │ + ├─ 拖到【另一列空白区】 + │ └─ 松手 → onColumnChange(任务, 目标列) + │ → 按 §2.2 场景 A 裁决 + │ → 乐观更新本地列与状态 → PATCH(失败回滚 + Toast) + │ + ├─ 拖到【另一列某卡片上】 + │ └─ 该卡片高亮为"落点" + │ 松手 → 若落点是顶层任务且被拖任务无子任务 ⇒ 降为子任务 + │ ├─ 是子任务 ⇒ 拒绝(T-ST-13) + │ ├─ 被拖任务有子任务 ⇒ 拒绝(T-ST-14) + │ └─ 拖到自己/后代 ⇒ 拒绝(T-EX-15) + │ + └─ 拖到【原列】→ 视为排序(本期仅更新 sortOrder) +``` + +**难点与对策**: + +| 难点 | 对策 | +| --- | --- | +| 移动端长按与滚动冲突 | 长按 300ms 后才进入拖拽态;进入后 `touch-action:none` 并锁定滚动容器 | +| 看板横向滚动 vs 拖拽换列 | 拖拽时容器允许横向自动滚动(边缘 24px 触发),松手位置决定列 | +| 落点判定(列 vs 卡片) | 用 `elementFromPoint` 命中 `[data-column-id]` / `[data-task-id]`,卡片优先 | +| 误操作 | 拒绝场景统一 Toast 说明原因;松手动画回弹原位 | +| 降为子任务后卡片消失 | 子任务不出独立卡片(T-ST-10),故从其原列移除,父任务卡片内出现该子任务行(T-ST-06) | + +> 兜底:无法拖拽时(如长按被系统手势占用),卡片「⋯」菜单提供「移动到列 / 移动到子任务」的等价操作(T-UI-07)。 + +### 3.2 保留选项的可见性计算与"当场消失" + +**窗口计算**(后端 `task-domain/retention.js`,自然日口径): + +``` +function retentionStart(option, now): + today = startOfDay(now) # Asia/Shanghai 00:00 + switch option: + never => 返回"不可见" + forever => 返回"无下界"(全可见) + today => today + 3d => today - 2 天 # 含今天共 3 天 + week => 本周一 00:00 + month => 本月 1 日 00:00 + +可见性判定:task.status == done + ? (doneAt != null && doneAt >= retentionStart(该任务所属列的 option)) + : true # 未完成任务始终可见(T-FL-12) +``` + +**判定依据**:`Task.doneAt`(完成时刻),**与任务当前所在列无关**(T-FL-10);归属哪一列决定用哪一列的选项(T-FL-11)。 + +**前端"当场消失"**(T-EX-18): + +``` +每分钟(以及跨日/跨周/跨月边界整点)执行一次: + 重新计算当前可见集合 + 对"由可见变不可见"的任务卡片: + 加 .is-leaving(透明 + 高度收缩,约 240ms) + 动画结束移除 DOM + 其余卡片通过布局动画平滑重排(不做全局刷新) +``` + +> 也允许退化为"刷新时计算"——需求明确接受该实现方式(§5 决策 23)。跨周期边界用**定时器 + 页面可见性变化(`visibilitychange`)**双保险。 + +### 3.3 跨项目拷贝 + +拷贝是**深拷贝**,且要处理目标项目成员差异(T-CP-07)与绑定差异(T-CP-09)。 + +``` +POST /api/tasks/:id/copy { projectId: 目标项目 } + │ + ├─ 校验:源任务可读、目标是本人成员(T-CP-06) + ├─ 事务内: + │ newTask = create({ + │ ...全部业务字段(标题/描述/状态/列/扩展卡片/标签) + │ creatorId = 当前操作者 ← T-CP-04 + │ projectId = 目标项目 + │ columnId = 目标项目中"列名相同"的列,找不到则取第一列 + │ }) + │ assigneeId = 目标项目成员?保留 : 置 null ← T-CP-07 + │ taskMembers = 过滤出目标项目成员中的交集 ← T-CP-07 + │ for 每个子任务:同法 create(parentId = newTask.id) ← T-CP-03、T-CP-08 + │ 状态与列不强行纠正(T-EX-14) + └─ 返回 { task, dropped: { assignee, members[] } } ← 提示被丢弃的人员 +``` + +**新旧任务完全独立**(T-CP-05):拷贝后无任何关联字段,是两条独立记录。 + +### 3.4 个人默认项目的创建与保护 + +``` +创建时机:用户首次访问 /api/projects(或注册时)→ 不存在 isPersonal 项目则创建 + name = `${user.name}的任务`(T-PJ-04,可改名) + +保护规则(服务端强制,前端不提供入口): + DELETE /api/projects/:id → isPersonal ⇒ 400(T-PJ-02、T-EX-09) + POST /api/projects/:id/members → isPersonal ⇒ 400(T-PJ-03、T-EX-09) + PUT /api/projects/:id → 允许改名(T-PJ-04) +``` + +### 3.5 成员清空后的项目 + +删除最后一名成员时**不删除项目**,只删 `ProjectMember` 记录(B-11、T-PJ-08): + +- 项目成为"无成员项目",任何人都不可见(T-PJ-10); +- 前端不做特判、不报错(T-EX-11); +- 数据保留,由后台维护流程后续处理(不在本模块范围,§1.5)。 + +> 副作用:曾为该项目任务的负责人 / 成员会显示为「已退出」(T-EX-06),因为其 `userId` 已不是项目成员。 + +--- + +## 4. 状态管理与性能 + +### 4.1 前端状态 + +| 状态 | 存放 | 生命周期 | +| --- | --- | --- | +| 当前项目、任务列表、列配置 | `TaskStore`(项目内单例) | 进入项目时加载,切换项目时替换 | +| 视图(list/board) | **设备端** `localStorage.iboard_task_prefs` | 按 `projectId` 分键(T-VW-03/06) | +| 上次访问项目 | **设备端** 同上 | 单个 key(T-VW-07) | +| 折叠状态(子任务) | 内存(组件内) | 会话内,不持久化 | +| 弹窗 / 拖拽态 | 组件内 | 操作结束即清 | + +### 4.2 性能设计 + +| 场景 | 目标 | 手段 | +| --- | --- | --- | +| 首屏加载(T-NF-01) | < 1.5s | 任务与列配置**一次请求返回**;子任务随父任务同批返回 | +| 交互响应(T-NF-01) | < 300ms | 乐观更新,不等接口返回 | +| 千级任务(T-NF-05) | 可用 | 索引覆盖(`projectId+columnId`、`projectId+status`);列表虚拟化(按需) | +| 保留计算 | 不额外请求 | 前端本地按 `doneAt` + 列选项实时过滤,切换档位零请求 | +| 看板拖拽 | 60fps | 拖拽期只用 transform,不改布局;落下后才重排 | + +### 4.3 一致性与容错 + +- **乐观更新 + 回滚**:失败时恢复快照并 Toast(T-EX-02)。 +- **幂等**:拖拽/勾选 500ms 内重复提交合并(T-EX-01)。 +- **配置变更即时生效**:保存列配置后服务端返回最新列列表,前端整体替换并重算可见集合,无需刷新(T-BD-12)。 + +--- + +## 5. 风险与权衡 + +| # | 风险 / 权衡 | 说明与对策 | +| --- | --- | --- | +| 1 | **列与状态双维度带来的认知成本** | 默认配置下二者一一对应(除 Action),用户"不懂也不会错";只有主动改配置时才需要理解绑定 | +| 2 | **自动归位可能让用户"找不到"任务** | 任务从当前列跳走。对策:归位后该卡片带一次高亮动画;提供 Toast「已移至 <列名>」 | +| 3 | **保留选项与"未绑定完成的已完任务"** | 状态为 done 但所有列都不绑定 done 时,任务停在不绑定列,其可见性按该列选项判定(T-FL-11)——语义自洽但需要文案说明 | +| 4 | **列可增删导致看板横向过宽** | 上限暂定 10 列(T-COL-06,**待确认**);超出时看板横向滚动 + 列头悬浮 | +| 5 | **子任务状态与父任务不一致的展示** | 卡片内嵌子任务带**小状态标记**(T-ST-09),避免"父已完成但子未做"的信息丢失 | +| 6 | **跨项目拷贝丢弃人员** | 返回被丢弃的名单并在前端提示,避免静默丢数据(T-EX-08) | +| 7 | **无成员项目的数据残留** | 本期不处理(T-PJ-08),需后台维护;在数据库设计文档中标注为"孤儿项目" | +| 8 | **前后端日期口径不一致** | `date.js` 双份实现是既有项目的做法;本期继续沿用并在两处加注释互指,写单测覆盖边界(周日 23:59 / 周一 00:00 / 月末) | + +--- + +## 6. 测试与验证要点 + +| 类别 | 要点 | +| --- | --- | +| **领域规则单测** | §2.3 判定矩阵 10 条全覆盖;`retentionStart()` 六档边界(含周日/月末跨点) | +| **接口测试** | 非项目成员访问 403;个人默认项目的两个保护接口返回 400;拷贝后源任务不变 | +| **交互验证** | 拖拽换列 / 降为子任务 / 拒绝场景 Toast;保留档位切换任务当场消失并重排 | +| **兼容性** | Android WebView + 移动浏览器(T-NF-02);375 / 390 / 430 三档视口 | +| **两视图一致** | 列表与看板可见条数一致(T-FL-11、T-FL-14);任一处修改另一处即时可见(T-UI-03) | + +--- + +## 7. 重点设计(2026-10-24 二轮评审) + +### 7.1 任务拖动方案(重做) + +旧版(手指跟随浮层 + 落点命中即转子任务)在真机上不可靠:浮层遮挡视线、touch 事件与滚动冲突、拖到卡片上误触转子任务。重做后的方案收敛为「**一个可靠手势 + 一个可靠菜单**」: + +| 场景 | 交互 | 实现 | +| --- | --- | --- | +| 看板换列 | 长按 400ms 激活(激活前移动 >10px 视为滚动,自动取消)→ 卡片**原位抬起**(不跟随手指、无浮层、无任务名)→ 指针划过列高亮 → 松手落列 | `useColumnDrag`:Pointer Events + `setPointerCapture`(触摸/鼠标统一、捕获后不丢事件、天然吞掉 pointercancel);`elementFromPoint` 命中 `[data-column-id]`;指针靠近容器边缘时 rAF 自动横滚,可拖到屏幕外的列 | +| 列表移动 | 长按 500ms → 弹「移动到列」ActionSheet(当前列打勾) | `TaskRow` 长按计时 → `ProjectView.openMoveMenu`;列表是纵向滚动流,不做自由拖动 | +| 转为子任务 | 不走拖拽(已移除「拖到卡片上」路径,误触率高) | 详情页「层级」区:选父任务转为子任务 / 一键转为顶层 | + +关键细节:卡片 `touch-action: pan-y`(纵向滚动留给浏览器,水平位移给拖动);激活与提交都以同一元素的 pointer 事件为准;`pointercancel` 与 `pointerup` 同样清理状态。换列后的状态仍由服务端按目标列绑定统一裁决(§2.2 不变)。 + +### 7.2 键盘遮挡统一方案 + +多个底部弹层(任务详情、信息块编辑、人员选择、拷贝)都出现过"输入框被输入法遮挡"。逐个组件接键盘逻辑不可维护,统一为**一个全局信号 + 一个 CSS 类**: + +1. **信号源**:`App` 级唯一监听器 `useKeyboardInsetWatcher`,同时监听 `visualViewport` 的 resize/scroll 与 `window.resize`(配合 `index.html` 的 `interactive-widget=resizes-content`,Chromium 系 WebView 键盘弹出会整体收缩布局视口);以"见过的最大视口高"为基线计算键盘插入量,写入 CSS 变量 **`--kb-inset`**(>90px 才视为键盘,过滤地址栏抖动)。 +2. **消费端**:底部弹层统一挂 **`.kb-safe`** 类 —— `padding-bottom: calc(安全区 + 20px + var(--kb-inset))`、`max-height` 用 `dvh` 随视口收缩并内部滚动。组件不再各自 import 键盘 hook、不再写内联 padding。 +3. **兜底**:长输入区(子任务输入、信息块 textarea)聚焦 300ms 后 `scrollIntoView({block:'center'})`,双保险。 + +新增弹层只要挂 `.kb-safe` 即自动获得键盘避让,无需再处理。 diff --git a/docs/V0.2/任务模块/任务模块需求文档.md b/docs/V0.2/任务模块/任务模块需求文档.md new file mode 100644 index 0000000..126d0a5 --- /dev/null +++ b/docs/V0.2/任务模块/任务模块需求文档.md @@ -0,0 +1,605 @@ +# iBoard V0.2 任务模块 需求文档 — 轻量任务 / 项目管理 + +> 版本:V0.2 · 模块:任务(Task) · 日期:2026-10-24 +> 适用范围:iBoard APP 内的任务模块(沿用现有项目框架、路由体系与登录态实现) +> 关联文档:《设计文档》《数据库设计文档》《界面设计》——接口清单在**设计阶段**重新设计,本文档不含接口定义 + +--- + +## 1. 总体需求 + +### 1.1 一句话描述 + +在 iBoard 现有 APP 内新增一个**轻量级的项目与任务管理模块**:界面交互参考 Todoist,以**项目**为隔离单位组织任务,提供**列表 / 看板**双视图,并允许**每个项目独立配置看板列与任务状态的对应关系、以及每列已完成任务的保留选项**,用最小认知成本覆盖「个人琐事记录」与「多人协作推进」两类场景。 + +**「轻量」的含义**:不做需求管理、工时、甘特图、工作流引擎、审批流;字段克制、结构最多两层(任务 + 一级子任务)、权限不做细分角色。目标是用最少的配置让用户"打开就能记、记完就能推进"。 + +### 1.2 核心概念 + +| 概念 | 定义 | +| --- | --- | +| **项目(Project)** | 任务归属的隔离单位,**也是权限单位**。用户只能看到自己是成员的项目;项目内成员权限目前均等 | +| **个人默认项目(Personal Project)** | 每个用户自动拥有且仅有一个的私有项目。**不可删除、不可添加其他成员**,名称以用户名开头且可自定义,用于个人琐碎事务 | +| **普通协作项目(Shared Project)** | 用户新建的项目,可增删成员、可归档 | +| **项目成员(Member)** | 用户与项目的多对多关联。成员的**存在**决定权限,「负责人 / 任务成员」字段只决定**展示** | +| **任务(Task)** | 一条事务记录。归属唯一项目;含基础字段 + 内置可折叠的**扩展信息卡片**;特例:子任务 | +| **一级子任务(Subtask)** | 挂在父任务下的任务。**最多一层**,不可再嵌套;必须与父任务同项目;完成状态与父任务**相互独立** | +| **负责人(Assignee)** | 任务上的**单人**标记,字号更大、视觉突出。**新建任务时默认填入创建者本人**,可清空 | +| **任务成员(Task Members)** | 任务上的**多人**标记,字号更小、弱化展示,位于卡片 / 条目下方 | +| **扩展信息卡片** | 任务内置的可折叠信息区,承载**任意名称 / 值 / 链接**的扩展信息(来源不限、字段不固定),**默认收起、全部非必填** | +| **看板列(Column)** | 项目级可配置的列,**默认 4 列** `Plan → Doing → Action → Done`;**可增删改、可调整顺序**,数量不固定 | +| **任务状态(Status)** | 任务的处理阶段,取值为 `未开始` / `执行中` / `完成`。**与看板列是两个独立概念**,二者的对应关系由项目配置决定 | +| **列-状态绑定(Column Status Binding)** | 项目级配置:**每一列独立**选择"是否绑定任务状态"以及"绑定到哪个状态"。绑定后列与状态双向联动;不绑定时二者完全独立 | +| **已完成但未绑定状态** | 任务状态为「完成」、但其所在看板列未绑定到「完成」的情形——此时列保持不变 | +| **看板列保留选项(Column Retention)** | 项目级配置:**每一列独立**设置已完成任务的保留时长,取值为「永不显示」/「当天」/「3 天」/「本周」/「本月」/「永久显示」六档,用于控制该列中已完成任务的可见性 | +| **视图记忆** | 设备端按项目独立保存"上次选择的视图(列表 / 看板)",进入项目自动恢复 | +| **上下文** | 用户当前所处的视图位置(某个项目 / 某个父任务的子任务列表 / 某个列表分区),用于解释"本次修改"相对哪些任务生效 | + +### 1.3 完整功能 + +1. **视图体系**:列表视图、看板视图,可随时互相切换,同源数据、实时双向同步;每个项目独立记忆上次使用的视图。列表只是不按列分组,**任务归属的看板列与看板视图完全一致**。**首屏**上部为「最近项目」(3 个卡片 + 更多入口,卡片可手动排序),下部直接展示**当前项目**的任务管理视图,不必先进项目列表。 +2. **看板与任务状态的绑定配置**: + - 看板列**默认 4 列** `Plan → Doing → Action → Done`;列**可增删改、可调整顺序**,数量不固定。 + - **每一列独立**选择是否绑定任务状态,并可选择绑定到 `未开始 / 执行中 / 完成` 中的任一个;**允许多列绑定同一状态**。 + - **列决定状态**:任务拖入某列时,该列绑定了状态则状态随之改变;该列未绑定则**保留原状态**。 + - **状态与列不匹配时自动归位**:状态改变后若当前所在列未绑定该状态,则按**当前列顺序**移入第一个绑定该状态的列;**没有匹配列就不移动**,保留当前位置。 + - **默认**:`Plan` 绑定「未开始」、`Doing` 绑定「执行中」、`Done` 绑定「完成」、`Action` **不绑定**。 + - 配置是**项目级**的,项目内任意成员均可修改,不同项目互不影响。 + - **子任务不受列-状态绑定约束**,其状态可与父任务不一致。 +3. **已完成任务显示控制**:**按看板列独立配置保留选项**——`永不显示` / `当天` / `3 天` / `本周` / `本月` / `永久显示` 六选一;超出保留选项的已完成任务在该列中自动隐藏。列表视图按其所属列的配置执行同样的显示控制。 +4. **任务字段体系**:基础字段(标题必填,负责人默认本人)+ 内置可折叠**扩展信息卡片**(默认收起、全非必填);负责人(单人)与任务成员(多人)均为信息标记。 +5. **负责人 & 成员展示规则**:负责人大字号突出、成员小字号弱化并置于下方;二者只能从**当前项目成员**中选择;不改变项目统一读写权限。 +6. **子任务体系**:仅一级、必须同项目、父子完成状态互不联动;列表视图支持缩进与折叠展开;**看板卡片内以简洁形式嵌套展示子任务并可直接勾选**,完整详情在卡片弹窗内编辑;支持**长按拖拽把任务降为另一个任务的一级子任务**。 +7. **项目体系**:个人默认项目(不可删、不可加成员、名称以用户名开头且可改名);普通协作项目(可增删成员);创建人可主动退出;成员清空后静默保留,交后台维护。成员管理、列设置与项目信息(改名 / 归档 / 删除 / 退出)统一收进「**项目管理**」入口。 +8. **项目权限**:以项目为隔离单位,只能看到自己是成员的项目;项目内成员权限均等、全读写;预留权限分级入口但不实现细分角色;任意成员均可归档项目。 +9. **跨项目拷贝**:是**拷贝**不是移动;完整复制全部字段、扩展信息、子任务、负责人、成员、状态、看板列、标签;新任务创建人为执行拷贝者;拷贝后新旧任务完全独立。 +10. **UI 交互规范**:参考 Todoist 的列表交互与子任务折叠风格;看板卡片轻量化;列表与看板同源、切换无刷新、数据一致;具备空状态 / 加载状态 / 数据异常状态。 +11. **模块入口**:任务模块作为**顶级 Tab** 接入底部导航,位于**「打卡」之后**(顺序:打卡 → 任务 → 宏观 → 监控 → 我)。 + +### 1.4 典型用法 + +**场景 A · 个人琐事(个人默认项目)** +用户打开模块 → 进入自己的个人默认项目(如「张三的琐事」)→ 列表视图输入标题回车新增,**负责人自动是本人**,也可清空 → 需要推进时切到看板,把任务从 `Plan` 拖到 `Doing`。 +因个人项目**不能加成员**,负责人 / 成员的可选范围只有自己,无需任何权限配置。 + +**场景 B · 多人协作与列-状态绑定** +用户新建普通项目「APP 改版」并加入 3 名同事。默认配置下:任务在 `Plan`=未开始、`Doing`=执行中、`Done`=完成;`Action` 不绑定状态,任务放进去后状态保持不变。 +成员 A 把任务拖入 `Done` ⇒ **状态自动变为「完成」**;成员 B 在列表视图把某任务状态改为「完成」⇒ 任务**自动出现在 `Done` 列**。 +若任务停留在 `Action`(不绑定)里被改成「完成」,因当前列与状态不匹配,系统**自动归位**到第一个绑定「完成」的列(默认即 `Done`);若该项目把「完成」的绑定全部取消,则**无匹配列,任务留在 `Action` 不动**。若把 `Action` 也设为绑定「完成」,则按列序仍先进 `Done`——这是**项目级**配置,不影响其他项目。 + +**场景 C · 扩展信息卡片** +新增任务「登录接口返回 500」→ 展开默认收起的**扩展信息卡片** → 按需添加任意条目,如「仓库 = gitlab/app」「分支 = fix/login-500」或一条相关链接(全部非必填,不填不影响任务本身)。 + +**场景 D · 跨项目复用** +把「APP 改版」里的某任务跨项目拷贝到「APP 二期」→ 生成一条**全新任务**,字段、扩展信息、子任务、负责人、成员、状态、看板列、标签全部复制,创建人记为执行拷贝的用户;此后两任务互不影响。 + +**场景 E · 按列控制已完成任务显示** +某项目的 `Done` 列已完成任务过多影响阅读 → 把该列保留选项设为「当天」→ 更早的已完成任务自动隐藏;同时把 `Action` 列设为「永不显示」,该列不再展示已完成任务;`Plan` 列保持「永久显示」不受影响。列表视图同样按各任务所属列的配置隐藏它们,与看板一致。切到另一个项目时,该项目各列配置保持自己的设置(互不干扰)。 + +**场景 F · 拖拽调整层级** +列表视图中长按任务卡片,向上拖动到另一个任务上并停留 → 该任务成为目标任务的**一级子任务**(缩进显示)。若目标任务本身已是子任务,或被拖任务已带子任务,则拒绝并给出提示(仅支持一级)。降为子任务后,它原有的看板列与状态保持不变。 + +**场景 G · 看板卡片内的子任务** +看板中某任务卡片下方以简洁样式嵌套显示其子任务 → 直接在卡片内勾选子任务完成,**父任务状态不受影响**,子任务状态也不受该列绑定规则约束;每个嵌套子任务带**自身状态小标记**,便于识别与父任务不一致的状态;需要编辑子任务标题或负责人时,点开卡片弹窗操作。 + +**场景 H · 从底部 Tab 进入(首屏)** +底部导航顺序为「打卡 → **任务** → 宏观 → 监控 → 我」→ 点击「任务」进入首屏:**上部**是最近使用的 3 个项目卡片 + 「更多项目」入口(点卡片即切换当前项目,长按卡片可调整顺序);**下部**直接是当前项目(上次访问的,记录在设备端;无记录则回到个人默认项目)的任务管理页,无需先进项目列表。点「更多项目」才进入完整的项目列表页。 + +**场景 I · 项目管理** +在项目页头部点「项目管理」→ 弹出统一管理页,内含三个分区:**成员**(查看 / 添加 / 移除)、**列设置**(列管理 / 状态绑定 / 保留选项)、**项目信息**(改名 / 归档 / 删除 / 退出,个人默认项目不出现删除与退出)。 + +### 1.5 不在本期范围(明确排除) + +- 多层子任务(仅一级)。 +- 角色 / 权限分级(只预留入口,本期全部成员权限均等)。 +- 任务评论、@提及、附件上传、提醒通知、日历同步。 +- 列内拖拽排序的精细规则(本期为简单位置值)。 +- 甘特图、工时、报表统计。 +- 项目成员清空后的数据清理(由后台维护,前端不做特殊处理)。 +- 独立的 WebView / 登录态 / 返回键适配(**沿用现有项目框架**,不单独提出要求)。 + +--- + +## 2. 术语与概念定义 + +| 术语 | 定义 | +| --- | --- | +| **项目(Project)** | 任务与权限的隔离单位;用户只能看到自己为成员的项目 | +| **个人默认项目** | 每个用户唯一、自动创建、不可删除、不可添加成员的私有项目;`isPersonal = true` | +| **普通协作项目** | 可增删成员、可归档的项目;`isPersonal = false` | +| **项目成员(Member)** | `ProjectMember` 记录,决定**读写权限** | +| **负责人(Assignee)** | 任务上的单人标记(须为项目成员),决定列表 / 卡片视觉上的突出位置;新建时默认为创建者,可清空 | +| **任务成员(TaskMember)** | 任务上的多成员标记(须为项目成员),弱化展示,可为空 | +| **任务状态(status)** | `todo`(未开始)/ `doing`(执行中)/ `done`(完成),三值枚举 | +| **看板列(column)** | 项目级可配置的列实体,默认 `Plan` / `Doing` / `Action` / `Done` 四列;可增删改、可排序,数量不固定 | +| **列-状态绑定(columnBindings)** | 项目级配置:每列独立声明「是否绑定」及「绑定到哪个状态」;默认 plan→todo、doing→doing、done→done、action→不绑定 | +| **已完成但未绑定状态** | 状态为 `done` 但所在列未绑定到 `done`;此时拖拽/勾选只改状态不改列(见 T-EX-04) | +| **看板列保留选项(columnRetention)** | 项目级配置:每列独立设置已完成任务的保留时长,`never` 永不显示 / `today` 当天 / `3d` 3 天 / `week` 本周 / `month` 本月 / `forever` 永久显示 | +| **视图记忆(viewMode)** | `list` / `board`,**按项目独立记忆、保存在设备端** | +| **扩展信息卡片** | 任务的折叠信息区,存任意 `[{label, value, url?}]` 条目,默认收起、全部非必填 | +| **一级子任务** | `parentId` 非空的任务;其父任务必须为顶层任务,且同项目 | +| **上下文** | 本次操作所处位置(项目 / 父任务 / 列表分区),用于界定"从该列拖出"等相对语义的作用范围 | +| **拷贝(Copy)** | 跨项目生成新任务并复制全部内容;原任务不受影响 | + +--- + +## 3. 需求点 + +**编号规则**:`T-模块-序号`。前缀 `T` 表示本模块(Task / 任务)。模块: + +| 模块码 | 模块名 | 对应 §1.3 条目 | +| --- | --- | --- | +| `VW` | 视图体系 | 第 1 条 | +| `COL` | 看板列管理 | 第 2 条 | +| `BD` | 看板与列-状态绑定 | 第 2 条 | +| `FL` | 已完成任务显示控制(按列保留选项) | 第 3 条 | +| `TK` | 任务字段体系(含扩展信息卡片) | 第 4 条 | +| `AS` | 负责人与成员 | 第 5 条 | +| `ST` | 子任务体系 | 第 6 条 | +| `PJ` | 项目体系 | 第 7 条 | +| `PM` | 项目权限与模块入口 | 第 8 条、第 11 条 | +| `CP` | 跨项目拷贝 | 第 9 条 | +| `UI` | UI 交互规范 | 第 10 条 | +| `DT` | 数据需求 | — | +| `NF` | 非功能需求 | — | +| `EX` | 异常与边界 | — | + +**优先级**:`P0` = 本期必须有;`P1` = 本期应该有;`P2` = 有则更好(可延后)。 + +**验收要点写法**:统一采用「给什么输入 / 做什么操作 → 期望得到什么可观察结果」,可直接转写为测试用例。 + +### 3.1 视图体系(T-VW) + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| T-VW-01 | 项目内提供**列表视图**与**看板视图**两种呈现方式 | 同一项目的同一批任务,两种视图均能完整呈现,条数一致 | P0 | +| T-VW-02 | 可在列表 / 看板之间**互相切换**,切换过程**无整页刷新** | 切换不触发页面重载、不出现白屏,切换后滚动位置或回到顶部行为明确 | P0 | +| T-VW-03 | **视图记忆按单个项目独立保存**:A 项目选列表、B 项目选看板互不影响 | 项目 A 切到看板 → 进入项目 B → B 仍为 B 上次选择的视图;回到 A → 仍为看板 | P0 | +| T-VW-04 | 进入项目时**自动恢复**该项目上次选择的视图 | 退出模块后重新进入某项目,视图与上次退出时一致 | P0 | +| T-VW-05 | 从未选择过视图的新项目,**默认使用列表视图** | 新建项目首次进入为列表视图 | P1 | +| T-VW-06 | 视图记忆保存在**设备端**,不要求跨设备同步;本机跨会话保持 | 杀进程重进某项目,视图仍为上次选择;换设备登录不要求一致 | P0 | +| T-VW-07 | **上次访问的项目**记录在**设备端**;无记录时**一律回到个人默认项目** | 杀进程重进落在上次项目;首次使用或记录失效则进入个人默认项目 | P0 | +| T-VW-08 | 任务**首屏上部**展示**最近使用的 3 个项目**卡片,末尾放「更多项目」入口;点击「更多项目」进入完整项目列表页 | 最近项目按访问时间倒序取前 3;点卡片切换当前项目并高亮;点「更多」进入项目列表 | P0 | +| T-VW-09 | 任务**首屏下部**直接展示**当前选中项目**的任务管理视图(列表 / 看板,随该项目视图记忆);当前项目默认为上次访问的项目,无记录时为个人默认项目 | 进入任务 Tab 即见当前项目的任务,**无需先进项目列表**;切换首屏项目卡片后下部随之切换 | P0 | +| T-VW-10 | 首屏**最近项目卡片支持手动排序**(长按拖动调整顺序),排序结果存**设备端**;未手动排序时按最近使用时间自动排列 | 拖动后顺序持久化,杀进程重进保持;新近访问但未手动排序的项目不出现在固定卡位之外 | P1 | + +### 3.2 看板列管理(T-COL) + +> 看板列是**项目级可配置实体**:默认 4 列,允许增删改与排序。列与「状态绑定」「保留选项」是三条正交的配置。 + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| T-COL-01 | 新建项目**默认生成 4 列**:`Plan` / `Doing` / `Action` / `Done`,顺序即此 | 新项目看板渲染 4 列且名称、顺序与默认一致 | P0 | +| T-COL-02 | 支持**新增列**(自定义名称),新列默认追加在末尾 | 新增后看板多一列,位于最后一列之后,可正常拖入任务 | P0 | +| T-COL-03 | 支持**重命名列** | 改名后看板、列设置、任务归属处同步显示新名称 | P0 | +| T-COL-04 | 支持**删除列**;删除前需二次确认,并明确其中任务的处理方式 | 按 T-EX-16 约定迁移任务,不产生无归属任务 | P0 | +| T-COL-05 | 支持**调整列顺序**(拖动排序),顺序即看板显示顺序 | 拖动后顺序持久化,重新进入保持新顺序 | P0 | +| T-COL-06 | 列数量**不固定**,约束上下限:**至少 1 列、至多 10 列**(上限待确认) | 可增到多于 4 列;删到仅剩 1 列时禁止再删;达 10 列时禁止新增并提示 | P0 | +| T-COL-07 | 列名需**非空**且有长度上限,同一项目内**不允许重名** | 提交空名 / 重名时拒绝保存并提示 | P0 | +| T-COL-08 | 删除列时,若该列绑定了状态,绑定关系随之解除 | 删除绑定「完成」的列后,该状态不再有该列可归位 | P1 | +| T-COL-09 | 列配置属**项目级**,项目内任意成员可修改,不影响其他项目 | 成员 A 改列名,成员 B 同步可见;其他项目列不受影响 | P0 | +| T-COL-10 | 列表视图不展示列分组,但任务仍归属某一列(列可被增删后依然成立) | 删除/新增列后,列表视图中任务的列归属正确 | P0 | + +### 3.3 看板与列-状态绑定(T-BD) + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| T-BD-01 | 看板以**列**为单位组织任务,列由项目配置(默认 4 列,支持增删改与排序,详见 T-COL) | 看板按项目当前列配置渲染;调整列后任务归属随之正确呈现 | P0 | +| T-BD-02 | **每一列独立**配置是否绑定任务状态;绑定时可选择绑定到 `未开始` / `执行中` / `完成` 中任一个 | 每列都有独立的绑定开关与状态选择器;各列配置独立保存 | P0 | +| T-BD-03 | 允许**多列绑定到同一个状态**(如 `Done` 与 `Action` 都绑定「完成」) | 同一项目配置两列均绑定「完成」后,两列均能触发完成联动 | P0 | +| T-BD-04 | **绑定列 → 拖入即改状态**:任务落入绑定列时,任务状态改为该列所绑定的状态 | 把「未开始」任务拖入绑定「执行中」的列 → 状态变「执行中」;拖入绑定「完成」的列 → 状态变「完成」 | P0 | +| T-BD-05 | **状态 → 自动移入列**:任务状态改为某状态时,任务自动移入绑定该状态的列 | 列表视图把状态改为「完成」→ 切到看板,任务位于绑定「完成」的列 | P0 | +| T-BD-06 | 同一状态被多列绑定时,自动移入**按当前列顺序取第一个**匹配列,规则确定且可预期 | 两列均绑定「完成」时,任务状态改为「完成」后仅出现在列序靠前的那个绑定列,不重复出现 | P0 | +| T-BD-07 | **列决定状态,不看来源列**:拖动任务时,一律以「**目标列**的绑定配置」决定任务状态——目标列绑定了状态,则状态改为所绑定状态;**目标列未绑定,则任务保留原状态** | ①从绑定「完成」的列拖到绑定「执行中」的列 → 状态变「执行中」;②从任意列拖到**未绑定**的 `Action` → 状态保持不变 | P0 | +| T-BD-08 | **不绑定列**与任务状态**完全独立**:拖入、拖出该列以及在其他位置改变状态,均不改变该列归属以外的行为 | 把「执行中」任务拖入未绑定的 `Action` → 状态仍为「执行中」,仅列变化 | P0 | +| T-BD-14 | **状态与所在列不匹配时自动归位**:任务状态改变后,若其**当前所在列未绑定该状态**,则自动查找绑定该状态的列并将其移入 | 在未绑定的 `Action` 中把任务改为「完成」→ 任务自动移入绑定「完成」的列 | P0 | +| T-BD-15 | 自动归位时**按当前列的实际显示顺序取第一个匹配列**(列顺序可被用户调整) | 若两列都绑定「完成」,状态改为「完成」后移入排在前面那一列 | P0 | +| T-BD-16 | **无匹配列时任务不移动**,保留在原列(状态照常改变) | 取消所有列对「完成」的绑定后,把任务改为「完成」→ 状态为完成,列保持不变 | P0 | +| T-BD-09 | **默认配置**:`Plan` 绑定「未开始」、`Doing` 绑定「执行中」、`Done` 绑定「完成」、`Action` 不绑定 | 新建项目首次打开看板配置,四列取值与默认完全一致 | P0 | +| T-BD-10 | 绑定配置是**项目级**的,项目内**任意成员**均可修改,不同项目互不影响 | 成员 A 修改项目一配置成功;项目二配置不受影响;成员 B 也能修改 | P0 | +| T-BD-11 | 修改绑定配置**不批量改写**既有任务的状态与列,仅对之后的操作生效 | 把 `Action` 由不绑定改为绑定「完成」后,已在 `Action` 的任务状态保持原样 | P1 | +| T-BD-12 | 绑定配置变更后,看板与列表**即时一致**,无需手动刷新 | 保存配置后,看板列徽标与完成态立即反映新规则 | P1 | +| T-BD-13 | **子任务不受看板列-状态绑定影响**:子任务状态不因所在列而改写,也不触发「状态 → 移入列」 | 父任务在绑定「完成」的列,其子任务状态仍可为「执行中」且不被改写;修改子任务状态不使其离开当前列 | P0 | + +### 3.4 已完成任务显示控制(T-FL) + +> 本节的「保留选项」**以看板列为单位**配置,取代原先的「项目整体 5 档过滤」。 + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| T-FL-01 | **每一列独立**配置已完成任务的保留选项,配置项属于项目级 | 各列(含新增列)都有独立配置入口,修改一列不影响其他列 | P0 | +| T-FL-02 | 保留选项为**固定 6 档**:`永不显示` / `当天` / `3 天` / `本周` / `本月` / `永久显示` | 下拉仅此六项,不提供自由输入天数 | P0 | +| T-FL-03 | 「永久显示」:该列已完成任务**始终展示**,不受时间限制 | 设为永久显示后,历史任意时间完成的任务均可见 | P0 | +| T-FL-04 | 「永不显示」:该列已完成任务**始终不展示** | 设为永不显示后,该列无已完成任务卡片 | P0 | +| T-FL-05 | 「当天」:仅展示**完成于今天(自然日)**的已完成任务 | 今天完成的可见;昨天完成的次日即不显示 | P0 | +| T-FL-06 | 「3 天」:展示**完成于最近 3 个自然日(含今天)**的已完成任务 | 今天、昨天、前天完成可见,更早不显示 | P0 | +| T-FL-07 | 「本周」:展示**完成于本周(周一 00:00 起,至今天)**的已完成任务 | 周一起算;下周一进来看,上周完成的任务不再显示 | P0 | +| T-FL-08 | 「本月」:展示**完成于本月(当月 1 日起,至今天)**的已完成任务 | 当月 1 日起算;进入次月后,上月完成的任务不再显示 | P0 | +| T-FL-09 | 超出所选档位范围的已完成任务**自动隐藏**(无需手动操作) | 某列设为「当天」,次日进入该列,昨日完成的任务自动消失 | P0 | +| T-FL-10 | 保留判定以「该任务的**完成时间**」为准,与任务当前所在列无关 | 任务完成后再移动列,可见性仍按其完成时间与新列档位判定 | P0 | +| T-FL-11 | **列表视图与看板视图行为一致**:列表只是不按看板列分组展示,任务归属哪一列仍是哪一列,显示控制同样按**其所属列**的配置执行 | 看板中某列已隐藏的已完成任务,在列表视图中同样不出现;两视图可见条数一致 | P0 | +| T-FL-12 | 该配置仅影响**已完成任务**;未完成任务始终展示,不受保留选项影响 | 未完成任务在任何保留选项配置下均可见 | P0 | +| T-FL-13 | 显示控制为**展示层过滤**,不改变任务数据与状态 | 修改保留选项后,任务数据、状态、计数口径不发生变化 | P0 | +| T-FL-14 | 过滤后每列的计数与展示条数一致 | 列头显示 N 条时,该列实际可见卡片同为 N 条 | P1 | +| T-FL-15 | 保留选项配置在**项目内所有成员间共享**(属项目配置,非个人偏好) | 成员 A 修改后,成员 B 进入即按新配置显示 | P0 | + +### 3.5 任务字段体系(T-TK) + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| T-TK-01 | 任务包含**基础字段**:标题(必填)、描述、所属项目、看板列、任务状态、负责人、任务成员、标签、创建人、创建时间、更新时间 | 新建任务后上述字段均可正确保存与读取 | P0 | +| T-TK-02 | 标题为**唯一必填**字段,其余字段均可为空 | 仅填标题即可保存成功;仅清空标题时拒绝保存并提示 | P0 | +| T-TK-03 | 任务状态为三值枚举:`未开始` / `执行中` / `完成` | 任一任务的状态只能取这三个值之一,不存在第四种取值 | P0 | +| T-TK-04 | ~~内置扩展信息卡片~~ **已移除**(2026-10-24 评审):相关信息记录改由**项目信息块**承载(T-PJ-13) | — | 已取消 | +| T-TK-05 | ~~扩展信息条目~~ **随 T-TK-04 一并移除** | — | 已取消 | +| T-TK-06 | ~~扩展信息卡片折叠~~ **随 T-TK-04 一并移除** | — | 已取消 | +| T-TK-07 | ~~扩展信息非必填~~ **随 T-TK-04 一并移除** | — | 已取消 | +| T-TK-08 | 每条任务有**负责人(单人)** | 任务最多只有一个负责人,重复设置时以最后一次为准 | P0 | +| T-TK-09 | 新建任务时**负责人默认填入创建者本人** | 新建任务后未手动修改时,负责人即为创建者 | P0 | +| T-TK-10 | 负责人**允许清空为空** | 清空后任务可正常保存,展示为「未指派」 | P0 | +| T-TK-11 | 每条任务有**任务成员(多人)** | 可设置 0 ~ 多个成员,成员不重复 | P0 | +| T-TK-12 | 任务支持**标签** | 可添加多个标签,展示于条目 / 卡片 | P1 | +| T-TK-13 | 任务的创建人、创建时间、更新时间由系统维护,用户不可直接编辑 | 编辑任务不改变创建人与创建时间;任何修改刷新更新时间 | P1 | +| T-TK-14 | 任务标题收紧至**30 个汉字**;快速添加输入框允许输入 200 字,超过 30 字时自动在 30 字以内的**分隔符(空格/标点等)**处截断,前段作为标题,整体文本并入描述 | 输入长文本创建后:标题 ≤30 字且不在词中间切断;描述包含完整原文余文;直接编辑标题超 30 字时拒绝保存并提示 | P0 | +| T-TK-15 | 任务可选**时间标记**(完整日期存储),设置了时间标记的任务在列表行与看板卡片上以 `MM-dd` 小标签展示 | 详情页可选择/清除时间标记;设置后卡片出现 MM-dd 标签,清除后消失 | P1 | +| T-TK-16 | 快速添加输入框支持**时间词前缀**:以 今天/明天/后天/本周/下周/本月/下月 开头(后跟分隔符)时,自动为任务设置对应时间标记并把该前缀从标题中移除 | 输入「明天 交周报」→ 创建任务标题为「交周报」、时间标记为明天的日期;无匹配前缀时行为不变 | P1 | + +### 3.6 负责人 & 成员 UI 展示规则(T-AS) + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| T-AS-01 | **负责人**字号更大、视觉突出,位于条目 / 卡片的主要信息区 | 同名负责人与成员并排时,负责人字号明显更大且位于主位 | P0 | +| T-AS-02 | **任务成员**字号更小、弱化展示(低对比色),位于卡片 / 条目**下方** | 成员信息渲染在负责人 / 标题之下,视觉层次弱于负责人 | P0 | +| T-AS-03 | 负责人只能从**当前项目的成员**中选择 | 选择器只列出本项目成员;项目外用户无法被选中 | P0 | +| T-AS-04 | 任务成员只能从**当前项目的成员**中选择 | 选择器只列出本项目成员,不提供项目外用户 | P0 | +| T-AS-05 | 负责人为空(已清空)时展示「未指派」占位,正常渲染 | 清空负责人后条目 / 卡片显示「未指派」,可再次指派 | P0 | +| T-AS-06 | 人员被移出项目后,其负责人 / 成员标记按约定处理且不报错 | 见 T-EX-06 | P1 | +| T-AS-07 | 负责人、任务成员**仅为信息标记**,不改变项目统一读写权限 | 仅为负责人或仅为成员的用户,对项目读写能力与其他成员完全相同 | P0 | + +### 3.7 子任务体系(T-ST) + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| T-ST-01 | **仅支持一级子任务**,不支持多层嵌套 | 子任务上无「添加子任务」入口;后端拒绝以子任务为父任务创建任务 | P0 | +| T-ST-02 | 子任务**必须与父任务归属同一项目** | 尝试将子任务置于其他项目时被拒绝(含跨项目拷贝场景) | P0 | +| T-ST-03 | 父任务与子任务的**完成状态相互独立**、不自动联动 | 子任务全部完成,父任务状态不变;父任务完成,子任务状态不变 | P0 | +| T-ST-04 | **列表视图**支持子任务**缩进**展示 | 子任务在父任务下方缩进渲染,层级一眼可辨 | P0 | +| T-ST-05 | **列表视图**支持子任务**折叠 / 展开** | 折叠后子任务不显示且不占位;展开后恢复;折叠状态可感知 | P0 | +| T-ST-06 | **看板卡片内嵌套展示子任务**:含子任务的卡片中,子任务以**简洁形式嵌套**显示在任务卡片内 | 卡片内可见子任务列表(如紧凑单行:复选框 + 标题),样式轻量、不喧宾夺主 | P0 | +| T-ST-07 | 嵌套子任务可在**卡片内直接勾选完成**;完整详情(编辑标题 / 负责人 / 扩展信息等)在卡片弹窗内查看编辑 | 点击子任务复选框即切换其状态;点击卡片打开弹窗可编辑子任务全部字段 | P0 | +| T-ST-08 | **子任务状态与父任务状态可以不一致**,且子任务**不受看板列-状态绑定约束** | 父任务在 `Done`(绑定「完成」)时,其子任务可为「未开始」且不被改写 | P0 | +| T-ST-09 | 卡片内嵌套的子任务**显示自身状态小标记**(区分「未开始 / 执行中 / 完成」),以便识别与其父任务不一致的状态 | 父任务为完成、子任务为执行中时,卡片内可见子任务带「执行中」小标记 | P0 | +| T-ST-10 | 子任务不可作为看板独立卡片出现,只在其父任务卡片内嵌套 | 看板上一个任务只占一张卡,子任务不会单独成卡 | P1 | +| T-ST-11 | 删除父任务时**级联删除**其子任务 | 删除父任务后子任务一并删除,不产生孤儿数据 | P0 | +| T-ST-12 | **支持长按拖拽把任务降为另一个任务的一级子任务** | 列表视图长按任务 A 拖到任务 B 上释放 → A 成为 B 的子任务并缩进显示 | P0 | +| T-ST-13 | 拖拽成为子任务时,若目标任务**本身已是子任务**,则拒绝操作并提示 | 把任务拖到某个子任务上 → 操作被拒绝,给出「仅支持一级子任务」提示,数据不变 | P0 | +| T-ST-14 | 拖拽成为子任务时,若被拖任务是**有子任务的父任务**,则拒绝并提示(父任务不可整体挂到别的任务下) | 拖动含子任务的任务到其他任务上 → 拒绝,避免形成二级嵌套 | P0 | +| T-ST-15 | 拖拽成为子任务时,子任务的**看板列与状态保持自身原值**,仅改变父子归属 | 按 T-EX-13 约定处理,保证子任务与父任务同项目且层级合法 | P1 | + +### 3.8 项目体系(T-PJ) + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| T-PJ-01 | 每个用户**自动拥有且仅有一个个人默认项目** | 新用户首次进入模块即已存在个人项目;不可再创建第二个 | P0 | +| T-PJ-02 | 个人默认项目**不可删除** | 无删除入口;直接调用删除接口被拒绝 | P0 | +| T-PJ-03 | 个人默认项目**不能添加其他成员** | 成员管理入口不存在或不可用;调用添加成员接口被拒绝 | P0 | +| T-PJ-04 | 个人默认项目名称**以用户名开头**,且**整体名称可自定义修改** | 初始名称以用户名开头;改名后修改内容生效,仍保持「个人项目」性质 | P0 | +| T-PJ-05 | 支持**新建普通协作项目** | 可创建多个普通项目,创建者为成员 | P0 | +| T-PJ-06 | 普通项目**可以增删成员** | 添加 / 移除成员后,成员列表与可见性即时变化 | P0 | +| T-PJ-07 | **项目创建人可以主动退出项目** | 创建人退出后不再是成员,该项目不再对其可见 | P0 | +| T-PJ-08 | 项目**成员清空后静默保留**,交后台后续维护,前端不做特殊处理 | 成员全部退出后,前端不弹特殊提示、不出现错误;不可见 | P0 | +| T-PJ-09 | 项目可**归档**,归档项目与进行中项目区分展示 | 归档后从进行中列表移出,可在归档区找回 | P1 | +| T-PJ-10 | 项目列表按用户可见范围返回,仅含自己为成员的项目 | 非成员项目不出现在列表中 | P0 | +| T-PJ-11 | 项目的**成员管理、成员新增、列设置**统一收进「**项目管理**」入口;项目页头部不再散置多个功能入口 | 项目管理内含 成员 / 列设置 / 项目信息 三个分区;头部仅保留「项目管理」一个入口 | P0 | +| T-PJ-12 | 项目管理内提供**项目信息管理**:改名(T-PJ-04)、归档 / 取消归档(T-PJ-09)、删除项目(T-PJ-02,个人默认项目禁用)、退出项目(T-PJ-07,创建人可退) | 各操作在项目管理中可用且遵循既有约束;个人项目不出现删除与退出入口 | P0 | +| T-PJ-13 | 项目支持**信息块**:可自由添加任意多个长文本信息块(标题 + 多行内容,如 Readme / 项目说明),每块**独立编辑与删除**;属项目级数据,全体成员可见可编辑 | 可增删改任意块;内容支持多行长文本;所有成员看到同一份信息块 | P0 | + +### 3.9 项目权限(T-PM) + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| T-PM-01 | 权限**以项目为隔离单位**:用户只能看到自己是成员的项目及其任务 | 非成员访问某项目接口返回 403 / 404,列表不出现该项目 | P0 | +| T-PM-02 | 项目内**所有成员权限均等**,具备**全读写**能力 | 任意成员均可新增 / 编辑 / 删除任务、管理成员、修改列-状态绑定与保留选项配置 | P0 | +| T-PM-03 | 预留**权限分级入口**,本期**不实现**细分角色 | 数据结构或接口层保留扩展位;界面无角色分配功能 | P1 | +| T-PM-04 | 项目内**所有成员都可以归档项目** | 任意成员执行归档均成功 | P0 | +| T-PM-05 | 复用现有项目框架的登录态与鉴权,未登录访问被拒 | 无有效登录态访问返回 401 | P0 | +| T-PM-06 | 任务模块作为**顶级 Tab** 接入底部导航,位置在**「打卡」之后** | 底部 Tab 顺序为「打卡 → 任务 → 宏观 → 监控 → 我」;点击该 Tab 进入任务模块 | P0 | +| T-PM-07 | 进入任务模块后**默认落在上次访问的项目**(记录在**设备端**);**无记录时一律回到个人默认项目** | 首次进入 / 记录失效 → 个人默认项目;再次进入 → 上次离开时的项目;换设备不继承 | P0 | + +### 3.10 跨项目拷贝(T-CP) + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| T-CP-01 | 跨项目操作为**拷贝**,不是移动:源任务保留在原项目 | 拷贝后源任务仍存在于原项目且内容不变 | P0 | +| T-CP-02 | 拷贝时**完整复制全部字段** | 标题、描述、负责人、成员、标签、任务状态、看板列、扩展信息卡片逐项比对一致 | P0 | +| T-CP-03 | 拷贝时**完整复制子任务** | 子任务条数、标题、状态、列归属均与源任务一致 | P0 | +| T-CP-04 | 拷贝后**新任务的创建人**为本次执行拷贝操作的用户 | 新任务创建人 = 操作者,非源任务创建人 | P0 | +| T-CP-05 | 拷贝完成后**新旧任务互相独立**,后续修改互不影响 | 修改新任务标题 / 状态,源任务不变;反之亦然 | P0 | +| T-CP-06 | 拷贝目标必须是操作者**有权限**的项目 | 目标项目非本人成员时拒绝并提示 | P0 | +| T-CP-07 | 拷贝时负责人 / 成员若不属目标项目成员,按约定处理 | 按 T-EX-08 约定处理,不产生「项目外人」脏数据 | P1 | +| T-CP-08 | 拷贝包含子任务时,子任务与父任务**同属目标项目** | 拷贝后子任务项目归属与父任务一致 | P0 | +| T-CP-09 | 拷贝时若目标任务项目的**列-状态绑定规则不同**,按 T-EX-14 约定处理 | 状态与列不出现互相矛盾的组合 | P1 | + +### 3.11 UI 交互规范(T-UI) + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| T-UI-01 | 整体界面、列表交互、子任务折叠风格**参考 Todoist** | 列表条目结构、快速新增、勾选完成、子任务折叠的交互与 Todoist 同类操作一致 | P0 | +| T-UI-02 | **看板卡片轻量化**,样式简洁 | 卡片仅承载必要信息,无多余装饰;信息层级清晰 | P0 | +| T-UI-03 | 列表与看板**同源数据**,修改**实时双向同步** | 列表改标题 / 状态 → 切到看板立即可见;看板改 → 切到列表立即可见 | P0 | +| T-UI-04 | 视图切换**无刷新**、数据一致 | 切换过程中不发起整页加载,数据不发生丢失或错位 | P0 | +| T-UI-05 | 移动端适配:复用现有样式体系与底部导航,安全区不遮挡内容 | 375~430px 视口无横向滚动、无内容被遮挡 | P0 | +| T-UI-06 | 勾选完成有即时反馈(视觉状态变化),不阻塞后续操作 | 点击勾选后立即变化,无需等待接口返回才能继续操作 | P1 | +| T-UI-07 | 看板拖拽在移动端支持**长按拖拽**,并提供卡片「移动到」快捷项作为兜底 | 长按可拖起卡片;无法拖拽的场景可用「移动到」菜单完成同效果 | P0 | +| T-UI-08 | 具备**空状态**展示(无项目 / 无任务 / 无子任务等) | 各层级无数据时展示空态与引导入口,不出现空白页 | P0 | +| T-UI-09 | 具备**加载状态**展示 | 首屏与切换视图时展示加载态,不出现闪烁或错乱 | P0 | +| T-UI-10 | 具备**数据异常状态**展示,可重试 | 接口失败时展示异常态与重试入口,重试成功后恢复 | P0 | +| T-UI-11 | 弹层(任务详情等)**底部按钮与屏幕底边保持安全距离**,不被安全区 / 手势条遮挡 | 底部留白 ≥ 安全区高度 + 12px;小屏下弹层内容可滚动,删除等底部按钮始终完整可见可点 | P0 | + +### 3.12 数据需求(T-DT) + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| T-DT-01 | 新增 Prisma 模型:`Project`(含 `isPersonal`、列-状态绑定配置、列保留选项配置、`archived` 等)、`ProjectMember` | 字段见附录 A | P0 | +| T-DT-02 | 列-状态绑定配置**按列存储**,每列独立保存「是否绑定 + 绑定状态」,支持多列指向同一状态 | 任意数量列的配置可独立读写;两列绑定同一状态可正确落库 | P0 | +| T-DT-03 | 列保留选项配置**按列存储**,取值覆盖六档:`never` / `today` / `3d` / `week` / `month` / `forever` | 三档取值均可落库并正确读回 | P0 | +| T-DT-04 | 新增 Prisma 模型:`Task`(含 `parentId` 自关联、`assigneeId`、`status`、看板列、扩展信息卡片、`sortOrder`) | 字段见附录 A | P0 | +| T-DT-05 | 新增 Prisma 模型:`TaskMember`(任务成员关联)、扩展信息卡片结构、标签结构 | 字段见附录 A | P0 | +| T-DT-06 | 视图记忆按**设备端**存储,不要求落库 | 换设备登录不要求视图一致 | P0 | +| T-DT-07 | 建立必要索引:任务按项目查询、按父子关系查询、按状态查询 | 列表 / 看板 / 显示控制查询均走索引 | P0 | +| T-DT-08 | 提供迁移 SQL 并纳入 `prisma migrate deploy` 流程 | 与现有迁移流程一致 | P0 | +| T-DT-09 | 归档采用软删除语义,历史数据保留 | 归档不物理删除任务与项目数据 | P1 | + +### 3.13 非功能需求(T-NF) + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| T-NF-01 | 项目内任务列表首屏加载 < 1.5s(正常网络),交互响应 < 300ms | 真机可感知流畅 | P0 | +| T-NF-02 | 兼容当前 APP 的 WebView 运行环境与移动端浏览器 | 真机验证通过 | P0 | +| T-NF-03 | 前端代码组织遵循现有约定:`src/pages/task/*` + `src/components/*` + `server/routes/task*.js` | 结构一致 | P0 | +| T-NF-04 | 路由与接口挂载**沿用现有项目框架**的组织方式 | 与现有模块的组织方式一致 | P0 | +| T-NF-05 | 数据量按「单项目千级任务 / 年」设计,无需分表 | 千级任务下列表与看板可用 | P1 | + +### 3.14 异常与边界(T-EX) + +| 编号 | 需求描述 | 处理约定 | 优先级 | +| --- | --- | --- | --- | +| T-EX-01 | 重复点击勾选 / 重复拖拽 | 前端防抖 + 后端幂等,状态最终一致 | P0 | +| T-EX-02 | 接口失败 / 断网 | 乐观更新回滚 + 提示,可重试 | P0 | +| T-EX-03 | 绑定配置被改为「不绑定」时,已在该列的已完成任务 | 保持当前列与状态不变,等待用户后续操作 | P0 | +| T-EX-04 | 任务状态与所在列绑定不一致(如在不绑定的 `Action` 中改为「完成」) | 触发自动归位:移入第一个绑定该状态的列;**无匹配列则不移动**,保留原列(见 T-BD-14~16) | P0 | +| T-EX-05 | 任务被拖到**未绑定状态**的列(如 `Action`),其原状态如何处理 | 原状态**保持不变**,不因离开原列而回退(不存在「拖出即回退」规则) | P0 | +| T-EX-06 | 项目成员被移除,其仍担任某任务负责人 / 成员 | 保留历史标记,展示为「已退出」样式;不报错、不清空 | P1 | +| T-EX-07 | 删除父任务 | 级联删除其子任务,确认弹窗提示「将一并删除 N 条子任务」 | P0 | +| T-EX-08 | 跨项目拷贝时负责人 / 成员不属于目标项目 | 仅保留属于目标项目成员者,其余丢弃并在结果中提示 | P1 | +| T-EX-09 | 个人默认项目尝试添加成员 / 尝试删除 | 接口返回明确错误,前端不提供入口 | P0 | +| T-EX-10 | 某列设为「当天」等档位且该列无符合条件的已完成任务 | 展示该列空态,不报错 | P0 | +| T-EX-11 | 项目成员清空后的项目 | 静默保留,前端不报错、不特殊提示 | P0 | +| T-EX-12 | 子任务所属项目与父任务不一致(绕过前端直接调用) | 后端拒绝并返回错误 | P0 | +| T-EX-13 | 拖拽成为子任务时,子任务的**看板列与状态**如何取值 | 保持自身原值不变,仅改变父子归属 | P1 | +| T-EX-14 | 跨项目拷贝时,源任务的状态 / 列在目标项目绑定规则下无对应关系 | 沿用源值落库,不强行纠正;由后续用户操作自然修正 | P1 | +| T-EX-15 | 拖拽将任务放到自身或其后代上 | 拒绝操作并提示,避免形成环形父子关系 | P0 | +| T-EX-16 | 删除仍有任务的列 | 二次确认后,该列任务迁移至**相邻列**(或由用户指定目标列),不产生无归属任务 | P0 | +| T-EX-17 | 删除唯一剩余的列 | **拒绝操作**并提示「至少保留一列」 | P0 | +| T-EX-19 | 新增列时已达列数上限 | 拒绝新增并提示已达上限,不产生空列 | P1 | +| T-EX-18 | 已完成任务跨周期(周一零点 / 月初零点)后应隐藏 | **当场消失**并带移除动画,其余任务平滑重排,不留空白 | P1 | + +### 3.15 任务归档(T-ARC) + +> 与「已完成显示控制(T-FL)」的关系:T-FL 决定任务**看不看得见**(可逆的展示过滤), +> 归档决定任务**在不在主工作集**(数据层面的收纳)。隐藏是自动归档的前置条件,归档移出主列表, +> 可通过「已归档查询」恢复。 + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| T-ARC-01 | **手动归档**:任务 `⋯` 菜单增加「归档」;任意未归档任务(不限状态)均可归档;父任务归档时**连带全部子任务**一并归档;操作需二次确认(提示连带条数) | 归档后任务从列表/看板消失;子任务同步消失;确认弹窗条数正确 | P0 | +| T-ARC-02 | **自动归档**:项目级配置「**隐藏的任务**多久归档」,可选 `3 天后 / 一周后 / 一月后 / 永不`(默认永不);已隐藏(T-FL 保留窗口过期)的 done 任务到期后由系统自动归档 | 配置保存后生效;隐藏任务到期被自动归档;「永不」时永不自动归档;未隐藏的任务不受影响 | P1 | +| T-ARC-03 | **归档排除**:已归档任务从任务列表、看板、列计数、项目任务数统计中排除 | 归档后各视图与计数立即减少;恢复后回到原位 | P0 | +| T-ARC-04 | **已归档查询**:独立入口查看本项目已归档任务;支持关键词(标题)、任务类型、看板列、归档时间范围过滤;**游标分页**支撑大量数据(千级) | 过滤条件生效;分页可连续加载到底;千级数据下查询流畅 | P0 | +| T-ARC-05 | **恢复**:已归档任务可取消归档,回到原看板列(列被删除的场景因外键 `ON DELETE RESTRICT` 不存在),状态保持不变;父任务恢复连带子任务 | 恢复后任务与子任务重新出现在列表/看板原列 | P0 | +| T-ARC-06 | **彻底删除**:已归档任务可物理删除(含子任务),二次确认 | 删除后不再出现在已归档查询 | P1 | +| T-ARC-07 | **配置入口**:项目管理新增「归档设置」分区,展示与修改自动归档档位 | 修改后即时保存并对全项目生效 | P1 | +| T-ARC-08 | **权限口径**:项目成员均可归档/恢复/删除归档任务(权限均等口径同 T-PM-04) | 与其他写操作权限一致,无额外限制 | P1 | + +--- + +## 4. 优先级汇总(本期交付范围) + +- **P0(必须)**:T-VW-01~04、T-VW-06~09;T-COL-01~07、T-COL-09、T-COL-10;T-BD-01~10、13~16;T-FL-01~13、15;T-TK-01~11;T-AS-01~05、07;T-ST-01~09、11~14;T-PJ-01~08、10~13;T-PM-01、02、04~07;T-CP-01~06、08;T-UI-01~05、07~11;T-DT-01~08;T-NF-01~04;T-EX-01~05、07、09~12、15~17;T-ARC-01、03、04、05。 +- **P1(应该)**:T-VW-05、10;T-COL-08;T-BD-11、12;T-FL-14;T-TK-12、13;T-AS-06;T-ST-10、15;T-PJ-09;T-PM-03;T-CP-07、09;T-UI-06;T-DT-09;T-NF-05;T-EX-06、08、13、14、18、19;T-ARC-02、06、07、08。 + +--- + +## 5. 已决策问题(评审记录) + +以下问题已在 2026-10-24 评审中确认,作为实现依据: + +| # | 问题 | 决策 | +| --- | --- | --- | +| 1 | 列-状态绑定是单列还是多列? | **每列独立配置**,允许**多列绑定同一状态**;默认 plan→未开始、doing→执行中、done→完成、action→不绑定(T-BD-02、03、09) | +| 2 | 保留档位的时间边界按自然日还是滚动小时? | **自然日**(Asia/Shanghai,含今天 00:00 起);本周按周一起算、本月按 1 日起算 | +| 3 | 视图记忆 / 显示配置存在设备端还是服务端? | 视图记忆**设备端**、不跨设备;保留选项**项目级共享**(T-VW-06、T-FL-12、T-DT-06) | +| 4 | 删除父任务时子任务如何处理? | **级联删除**,弹窗提示条数(T-EX-07) | +| 5 | 已完成任务在看板中的呈现? | 由**该列的保留选项**控制显示与隐藏(T-FL-01~10) | +| 6 | 看板拖拽的移动端交互? | **长按拖拽** + 「移动到」兜底;**允许拖拽降为子任务**(T-UI-07、T-ST-12) | +| 7 | 项目成员清空后是否需要本模块的管理入口? | **本期不承担**,交后台;前端静默(T-PJ-08) | +| 8 | 扩展信息组的具体字段清单? | **不固定字段**,改为**任意扩展信息卡片**(T-TK-04、05) | +| 9 | 已完成显示策略的粒度? | 改为**按看板列独立的保留选项**(T-FL-01、02) | +| 10 | 是否单独要求 WebView / 登录态 / 返回键? | **不单列**,沿用现有项目框架(T-NF-04、T-PM-05) | +| 11 | 同一状态被多列绑定,状态变化时进哪一列? | **按当前列顺序取第一个匹配**(T-BD-06、15、B-3) | +| 12 | **从绑定列拖出**时状态是否回退? | **不设「拖出即回退」规则**。只看**目标列**:目标列绑定了状态就按目标列改,未绑定则**保留原状态**(T-BD-07、B-4) | +| 13 | 列表视图如何处理「按列」的显示控制? | 列表只是**不按列分组**,任务归属仍是原看板列,行为与看板**完全一致**(T-FL-11、B-6) | +| 14 | 看板卡片如何展示子任务? | **在卡片内嵌套简洁展示**,可直接勾选;完整详情在卡片弹窗(T-ST-06、07) | +| 15 | 子任务是否受列-状态绑定影响? | **不受**。子任务状态可与父任务不一致,拖入列不改写其状态(T-BD-13、T-ST-08、B-7) | +| 16 | 父任务能否整体挂到别的任务下? | **不能**。含子任务的父任务不可被拖拽为子任务(T-ST-14) | +| 17 | 保留选项的取值范围? | 固定 **6 档**:`永不显示` / `当天` / `3 天` / `本周` / `本月` / `永久显示`,不做自由输入(T-FL-02、B-5) | +| 18 | 任务状态与所在列绑定不一致时怎么处理? | **触发自动归位**:查找绑定该状态的列并移入,按列序取第一个;**无匹配列则不移动**(T-BD-14~16、B-4b) | +| 19 | 卡片内嵌套的子任务是否显示自身状态? | **显示小状态标记**,以区分与父任务不一致的状态(T-ST-09) | +| 20 | 任务模块入口放在哪里? | 作为**顶级 Tab**,位置在**「打卡」之后**(T-PM-06) | + +| 21 | 5 个底部 Tab 是否影响既有布局与默认首页? | **暂无障碍**;默认首页**仍为打卡**(T-PM-06) | +| 22 | 任务状态为「完成」但所有列都未绑定「完成」时,列表视图按哪一列过滤? | **保留在当前列**,按该列保留选项过滤。**不存在「已完成统一用 Done 列」的强制规则**,那只是默认配置下的自然效果,用户可完全自定义(T-FL-11) | +| 23 | 「本周 / 本月」跨周跨月时是否要求实时隐藏? | **当场消失并带移除动画,其余任务平滑重排**;以刷新方式实现也可接受(T-EX-18) | +| 24 | 「上次访问的项目」记录在哪?无记录时如何? | 记录在**设备端**;**无记录一律回个人默认项目**(T-VW-07、T-PM-07) | +| 25 | 看板列是否固定 4 列? | **非固定**:默认 4 列,支持增删改与排序,数量可变,至少保留 1 列(T-COL-01~07) | + +| 26 | 是否允许删除最后一个列? | **不允许**。删除唯一剩余列时拒绝并提示,项目至少保留 1 列(T-COL-06、T-EX-17) | +| 27 | 新增列的默认设置? | 按**默认值**:保留选项 `永久显示`、不绑定状态,**不继承**相邻列设置(T-COL-02、B-18) | +| 28 | 看板列数量上限? | 暂定**上限 10 列**(`待确认`,见下) | +| 29 | 任务首屏结构? | **上部** = 最近使用的 3 个项目卡片 + 「更多项目」入口;**下部** = 当前选中项目的任务管理视图,默认上次访问的项目(T-VW-08/09) | +| 30 | 首屏「任务卡片可以排序」的指代? | 指**最近项目卡片**可长按拖动排序,结果存设备端;任务卡片的列内排序维持既有口径(T-VW-10) | +| 31 | 成员 / 列设置入口的位置? | 统一收进「**项目管理**」入口(含成员、列设置、项目信息三区),项目页头部不再散置(T-PJ-11) | +| 32 | 项目信息管理包含什么? | 改名、归档 / 取消归档、删除(个人项目禁用)、退出(创建人可退),入口在项目管理内(T-PJ-12) | +| 33 | 任务详情底部按钮遮挡? | 弹层底部预留**安全区 + 12px** 距离;小屏下弹层可滚动,底部按钮完整可见(T-UI-11) | +| 34 | 项目相关长文本信息记录在哪? | 项目管理内新增**信息块**分区:任意多个 {标题, 长文本} 块,独立编辑,属项目级数据(T-PJ-13) | +| 35 | 任务级「扩展信息」的去留? | **移除 UI**(2026-10-24 评审):任务不再带扩展信息,项目相关长文本由项目信息块(T-PJ-13)承载;DB 字段保留不用 | + +**仍待评审确认(影响实现口径):** + +| # | 问题 | 说明 | +| --- | --- | --- | +| A | 列数量上限是否按 10 列? | 现按「至少 1 列、至多 10 列」写入(T-COL-06)。若无需上限,可去掉上限约束 | + +--- + +## 附录 A:核心实体草案(供设计阶段参考,非最终表结构) + +```prisma +// 项目 +model Project { + id Int @id @default(autoincrement()) + name String + ownerId Int // 创建人 (可退出项目, 故与成员表分离) + isPersonal Boolean @default(false) // true = 个人默认项目: 不可删、不可加成员 + archived Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + members ProjectMember[] + columns BoardColumn[] + tasks Task[] + + @@index([archived]) +} + +// 看板列 (项目级实体: 默认 4 列, 可增删改与排序) +model BoardColumn { + id Int @id @default(autoincrement()) + projectId Int + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + name String // 列名, 项目内唯一 + sortOrder Int @default(0) // 显示顺序, 决定自动归位时的"第一个匹配列" + boundStatus String? // null = 不绑定; todo | doing | done + retention String @default("forever") // never | today | 3d | week | month | forever + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + tasks Task[] + + @@unique([projectId, name]) + @@index([projectId, sortOrder]) +} + +// 项目成员 (权限单位: 在表内即全读写) +model ProjectMember { + id Int @id @default(autoincrement()) + projectId Int + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + userId Int + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + permission String @default("member") // 预留权限分级入口, 本期恒为 member + createdAt DateTime @default(now()) + + @@unique([projectId, userId]) + @@index([userId]) +} + +// 任务 / 子任务 (parentId 为空 = 顶层任务) +model Task { + id Int @id @default(autoincrement()) + projectId Int + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + parentId Int? // 非空 = 一级子任务 (最多一层) + parent Task? @relation("Subtask", fields: [parentId], references: [id], onDelete: Cascade) + children Task[] @relation("Subtask") + + title String + description String @default("") + status String @default("todo") // todo | doing | done + columnId Int // 所属看板列 (BoardColumn), 列可增删改故用 id 而非固定名 + column BoardColumn @relation(fields: [columnId], references: [id]) + + assigneeId Int? // 负责人 (可空; 新建默认 = 创建者) + assignee User? @relation("Assignee", fields: [assigneeId], references: [id], onDelete: SetNull) + creatorId Int // 创建人 (跨项目拷贝时 = 操作者) + creator User @relation("Creator", fields: [creatorId], references: [id]) + + // 扩展信息卡片: 任意条目, 默认收起 + extras Json @default("[]") // [{ label: "仓库", value: "gitlab/app", url: "" }] + + sortOrder Int @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + members TaskMember[] + tags TaskTag[] + + @@index([projectId, columnId]) + @@index([projectId, status]) + @@index([parentId]) +} + +// 任务成员 (多人, 须为项目成员) +model TaskMember { + id Int @id @default(autoincrement()) + taskId Int + task Task @relation(fields: [taskId], references: [id], onDelete: Cascade) + userId Int + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([taskId, userId]) +} + +// 任务标签 +model TaskTag { + id Int @id @default(autoincrement()) + taskId Int + task Task @relation(fields: [taskId], references: [id], onDelete: Cascade) + name String + @@index([taskId]) +} +``` + +> 说明: +> 1. `status` 与 `columnId` **两个字段都落库**(而非互相推导),因为未绑定的列需要二者彼此独立;联动仅在项目配置了绑定的列/状态之间发生,由服务端统一裁决。 +> 2. 保留选项的判定依赖「在该列的完成时间」,建议在任务上记录完成时间或按列迁移时间计算,具体口径在设计阶段确定。 +> 3. 视图记忆存设备端(localStorage),不落库。 + +--- + +## 附录 B:实现约定(开发期补充,用于消除口径歧义) + +| # | 约定 | 说明 | +| --- | --- | --- | +| B-1 | **看板列** | 列是项目级实体(`BoardColumn`),含名称与顺序;默认 4 列 `Plan/Doing/Action/Done`;支持增删改与排序,至少保留 1 列 | +| B-2 | **列-状态绑定** | `columnBindings` 为「列 → 状态或 null」映射;null 表示不绑定;**允许多列指向同一状态**;状态枚举 `todo` / `doing` / `done` | +| B-3 | **多列绑定同一状态时的目标列** | 移入列时按**当前列顺序**取**第一个**绑定该状态的列(列顺序可被用户调整) | +| B-4 | **列决定状态** | 拖动任务时只看**目标列**:目标列绑定了状态 ⇒ 状态 = 所绑定状态(含从任意列拖入);目标列未绑定 ⇒ 状态保持原值。**不存在「从某列拖出即回退」的规则** | +| B-4b | **状态与列不一致时的自动归位** | 状态改变后,若当前所在列未绑定该状态 ⇒ 按列序取第一个绑定该状态的列并移入;**无匹配列则不移动**,状态照常改变 | +| B-5 | **列保留选项** | `columnRetention` 为「列 → 选项」映射,选项为固定 6 档:`never`(永不显示)/ `today`(当天)/ `3d`(3 天)/ `week`(本周)/ `month`(本月)/ `forever`(永久显示) | +| B-5b | **各档位口径** | 均按 Asia/Shanghai 自然日:`today` = 今天;`3d` = 含今天在内的最近 3 天;`week` = 本周一 00:00 起;`month` = 本月 1 日 00:00 起;判定依据为任务的**完成时间** | +| B-6 | **显示控制作用域** | 列表视图与看板视图**同样**按任务**当前所属列**的保留选项执行过滤;列表只是不按列分组,归属不变;未完成任务不受影响 | +| B-7 | **子任务与绑定无关** | 子任务状态**不受**看板列-状态绑定影响:拖入列不改写子任务状态,修改子任务状态也不触发「移入列」 | +| B-8 | **子任务约束** | `parentId` 非空时:父任务 `parentId` 必须为空(禁止二级);`projectId` 必须与父任务一致;禁止指向自身或后代 | +| B-9 | **拖拽降为子任务** | 长按拖动任务 A 到任务 B 上释放 ⇒ A.parentId = B.id;要求 B 为顶层任务且 A 本身无子任务,否则拒绝 | +| B-10 | **负责人默认值** | 新建任务时若未显式指定,`assigneeId` 默认取创建者的 userId;显式传 null 表示清空 | +| B-11 | **个人默认项目** | `isPersonal = true`;服务端拒绝「添加成员」「删除项目」;名称初始为 `{用户名}的任务`,可改名 | +| B-12 | **成员清空** | 移除最后一名成员时不删除 `Project`,仅删除 `ProjectMember` 记录;前端不特判 | +| B-13 | **跨项目拷贝** | 新建任务(新 id)+ 复制全部字段、扩展卡片与子任务;`creatorId` = 操作者;负责人 / 成员按 T-EX-08 过滤;状态与列按 T-EX-14 沿用 | +| B-14 | **权限校验** | 所有项目 / 任务接口均先校验「请求者为该项目成员」,非成员返回 403 | +| B-15 | **偏好存储** | 视图记忆与「上次访问的项目」存设备端;列定义、列-状态绑定、列保留选项属项目配置,存服务端、项目内共享 | +| B-16 | **看板列实体** | 列为 `BoardColumn`(name / sortOrder / boundStatus / retention);默认 4 列由新建项目时初始化;至少保留 1 列;列名项目内唯一 | +| B-17 | **删除列的迁移** | 删除列前需二次确认;列内任务迁移至指定列(默认相邻列),迁移后任务状态按目标列绑定重新裁决;**唯一剩余列不可删除** | +| B-18 | **新增列默认值** | 新增列取默认值:`retention = forever`、`boundStatus = null`,**不继承**相邻列配置;`sortOrder` 追加到末尾 | +| B-19 | **列数上限** | 至少 1 列、至多 10 列(上限待确认) | diff --git a/docs/V0.2/任务模块/视图切换移入项目栏-设计稿.html b/docs/V0.2/任务模块/视图切换移入项目栏-设计稿.html new file mode 100644 index 0000000..75f5697 --- /dev/null +++ b/docs/V0.2/任务模块/视图切换移入项目栏-设计稿.html @@ -0,0 +1,108 @@ + + + + + +设计稿 · 视图切换移入项目栏 (T-VW-02/03 调整) + + + + +
+

状态 1 · 列表视图(默认)

+
切换段控放在项目名右侧, 与名称同一行
+
+
我的看板项目
+
列表
看板
+
已归档
+
管理
+
+ 新增
+
+
「项目管理」按钮文案收窄为「管理」, 与「已归档」同为次级样式, 突出右侧主操作
+
+
梳理 V0.2 归档需求
+
OTA 发布说明样式修复
+
看板列滚动条位置调整
+
+
+ 规格
+ · 分段控件高 26px, 选中项白底浮起(复用现有 .seg 体系收窄版)
+ · 项目名 flex:1 + 单行省略, 最短保留 4 字符
+ · 段控始终紧贴名称右侧, 距离 8px +
+
+ +
+

状态 2 · 看板视图

+
点「看板」后段控选中项滑换, 其余布局不变
+
+
我的看板项目
+
列表
看板
+
已归档
+
管理
+
+ 新增
+
+
+
+
Plan · 2
梳理归档需求
OTA 说明修复
+
Doing · 1
滚动条调整
+
Done · 1
版本方案落地
+
+
+
+ 交互
+ · 点按即切换并持久化(设备端记忆, T-VW-02/03 不变)
+ · 切换不引发项目栏高度变化, 下方视图平滑重排
+ · 长按/拖动手势不受影响(段控独立控件, 不在卡片上) +
+
+ +
+

状态 3 · 长项目名

+
名称被压缩时单行省略, 段控与按钮永不换行/收缩
+
+
经济数据整理与策略研究-2026年第一季度重点项目
+
列表
看板
+
已归档
+
管理
+
+ 新增
+
+
+
完整项目名在详情/管理页仍可见
+
+
+ 宽度预算 (375px)
+ · 左右内边距 24 + 段控 90 + 已归档 48 + 管理 42 + 新增 54 + 间距 32 ≈ 290
+ · 名称可用 ≈ 85px(约 6 个汉字)起, 不足部分省略
+ · 若仍嫌拥挤, 备选:「已归档」「管理」收进 ⋯ 溢出菜单(二期可选, 本稿未采用) +
+
+ + + diff --git a/docs/V0.2/数据库设计文档.md b/docs/V0.2/数据库设计文档.md new file mode 100644 index 0000000..003a9d4 --- /dev/null +++ b/docs/V0.2/数据库设计文档.md @@ -0,0 +1,325 @@ +# iBoard V0.2 数据库设计文档 + +| 项 | 内容 | +| --- | --- | +| 版本 | V0.2 | +| 文档状态 | 已实现(与代码一致) | +| 数据库 | MariaDB / MySQL 8(Prisma `provider = "mysql"`) | +| 字符集 | `utf8mb4 / utf8mb4_unicode_ci` | +| 时区约定 | 应用层统一 `Asia/Shanghai`,库内 `DATETIME` 存 UTC | +| 相关文件 | `prisma/schema.prisma`、`prisma/migrations/20261022120000_add_checkin_checklist/migration.sql` | +| 关联文档 | `需求文档.md`、`设计文档.md`、`界面设计.html` | + +--- + +## 1. 概述 + +V0.2 新增「打卡 / 日历 / 清单」三个功能域,共引入 **5 张表**: + +| 表 | 作用 | 归属域 | +| --- | --- | --- | +| `Habit` | 打卡项(每天/每周/每月/每年 n 次的目标定义) | 打卡 | +| `CheckIn` | 打卡记录(一次打卡 = 一条) | 打卡 | +| `CheckList` | 检查清单(出差/回老家等场景的一次性清单) | 清单 | +| `CheckListItem` | 检查项(勾选即完成) | 清单 | +| `CheckListTemplate` | 清单模板(系统预置 + 用户自定义) | 清单 | + +日历视图**不新增表**:完全由 `Habit` + `CheckIn` 按月聚合得出(见 §7.3)。 + +### 1.1 设计原则 + +1. **记录为真,进度不落库。** 环形进度、完成率、streak 一律由 `CheckIn` 现场聚合,不在 `Habit` 上冗余 `doneCount` 字段——避免并发计数不一致、以及用户中途把「每周 3 次」改成「每周 5 次」后需要回填历史的问题。 +2. **日期以本地日历日字符串存储。** `CheckIn.date` 用 `VARCHAR(10)` 存 `"YYYY-MM-DD"`(Asia/Shanghai 下的日历日),而不是 `DATETIME`:范围查询走索引、字典序等于时间序、天然幂等边界,且彻底规避「UTC 存 + 本地读」导致的跨零点归属错误。 +3. **软删除优先。** 打卡项删除默认走 `archived` 归档(保留历史统计),只有显式 `?hard=1` 才物理删除。 +4. **一切按 `userId` 隔离。** 所有业务表都带 `userId`,`Habit`/`CheckIn`/`CheckList`/`CheckListTemplate` 外键 `ON DELETE CASCADE` 到 `User`。 +5. **迁移只增不改。** 新增需求一律新迁移文件,不修改已执行的迁移(部署走 `prisma migrate deploy`)。 + +--- + +## 2. 概念模型(ER) + +``` + ┌──────────┐ + │ User │ (既有表, 本次仅新增反向关系) + └────┬─────┘ + ┌───────────────┼────────────────────┬─────────────────┐ + │ │ │ │ + ┌────▼────┐ ┌────▼────┐ ┌─────▼─────┐ ┌──────▼──────┐ + │ Habit │1───*│ CheckIn │ │ CheckList │1─* │CheckListItem│ + └─────────┘ └─────────┘ └───────────┘ └─────────────┘ + 打卡项 打卡记录 场景清单 检查项 + (周期+目标n) (habitId,date,seq) (done, doneAt) + ▲ + │ 生成(复制条目, 不建外键) + ┌─────┴──────────┐ + │CheckListTemplate│ + │ (builtin/自定义) │ + └────────────────┘ +``` + +基数说明: + +| 关系 | 基数 | 级联 | +| --- | --- | --- | +| User → Habit | 1 : N | Cascade | +| User → CheckIn | 1 : N | Cascade | +| Habit → CheckIn | 1 : N | Cascade | +| User → CheckList | 1 : N | Cascade | +| CheckList → CheckListItem | 1 : N | Cascade | +| User → CheckListTemplate | 1 : N(可空,系统预置为空) | Cascade | +| CheckListTemplate → CheckList | 无外键,创建时**复制**条目内容 | — | + +--- + +## 3. 表结构 + +### 3.1 `Habit` — 打卡项 + +周期目标的**定义**表,不含任何计数。 + +| 字段 | 类型 | 空 | 默认 | 说明 | +| --- | --- | --- | --- | --- | +| `id` | INT PK | 否 | AUTO_INCREMENT | 主键 | +| `userId` | INT FK | 否 | — | 所属用户 | +| `name` | VARCHAR(191) | 否 | — | 名称,接口校验 1~20 字 | +| `period` | VARCHAR(191) | 否 | `'day'` | `day` \| `week` \| `month` \| `year` | +| `target` | INT | 否 | `1` | 周期内目标次数 n(校验 ≥1) | +| `color` | VARCHAR(191) | 否 | `'#1677ff'` | 环形与标记主色(7 色预设之一) | +| `icon` | VARCHAR(191) | 否 | `''` | emoji / 图标名,**V0.2 预留不展示** | +| `note` | VARCHAR(191) | 否 | `''` | 备注 | +| `archived` | BOOLEAN | 否 | `false` | 归档后不出现在待办,历史保留 | +| `sortOrder` | INT | 否 | `0` | 排序权重(升序) | +| `createdAt` | DATETIME(3) | 否 | now() | 用于「创建之前的日期不计入月历期望」 | +| `updatedAt` | DATETIME(3) | 否 | 自动 | — | + +索引:`INDEX(userId, archived, sortOrder)` — 覆盖「取当前用户未归档打卡项」主查询。 + +### 3.2 `CheckIn` — 打卡记录 + +**一次打卡 = 一条记录**,同日多次用 `seq` 递增。 + +| 字段 | 类型 | 空 | 默认 | 说明 | +| --- | --- | --- | --- | --- | +| `id` | INT PK | 否 | AUTO_INCREMENT | 主键 | +| `habitId` | INT FK | 否 | — | 所属打卡项 | +| `userId` | INT FK | 否 | — | 冗余用户(用于单用户全量区间查询,避免 join) | +| `date` | VARCHAR(10) | 否 | — | 本地日历日 `"YYYY-MM-DD"` | +| `seq` | INT | 否 | `1` | 当日第几次(1..n,超额继续累加) | +| `note` | VARCHAR(191) | 否 | `''` | 单次备注(接口截断 200 字) | +| `source` | VARCHAR(191) | 否 | `'user'` | `user` \| `backfill`(补打) | +| `createdAt` | DATETIME(3) | 否 | now() | 打卡时刻(UTC) | + +约束与索引: + +| 名称 | 类型 | 字段 | 作用 | +| --- | --- | --- | --- | +| `CheckIn_habitId_date_seq_key` | **UNIQUE** | `(habitId, date, seq)` | 并发重复提交的幂等防线(冲突 → P2002 → 409) | +| `CheckIn_userId_date_idx` | INDEX | `(userId, date)` | 月历按月区间扫全量记录 | +| `CheckIn_habitId_date_idx` | INDEX | `(habitId, date)` | 单打卡项周期区间聚合 | + +### 3.3 `CheckList` — 检查清单 + +| 字段 | 类型 | 空 | 默认 | 说明 | +| --- | --- | --- | --- | --- | +| `id` | INT PK | 否 | AUTO_INCREMENT | 主键 | +| `userId` | INT FK | 否 | — | 所属用户 | +| `title` | VARCHAR(191) | 否 | — | 清单名,如「出差 · 杭州」 | +| `scene` | VARCHAR(191) | 否 | `''` | 场景:`出差` \| `回老家` \| `其他` | +| `note` | VARCHAR(191) | 否 | `''` | 备注 | +| `archived` | BOOLEAN | 否 | `false` | 归档 | +| `createdAt` / `updatedAt` | DATETIME(3) | 否 | — | 列表默认按 `updatedAt` 倒序 | + +索引:`INDEX(userId, archived)` — 对应「进行中 / 已归档」两个分段列表。 + +### 3.4 `CheckListItem` — 检查项 + +| 字段 | 类型 | 空 | 默认 | 说明 | +| --- | --- | --- | --- | --- | +| `id` | INT PK | 否 | AUTO_INCREMENT | 主键 | +| `listId` | INT FK | 否 | — | 所属清单 | +| `title` | VARCHAR(191) | 否 | — | 条目名(校验 ≤40 字) | +| `note` | VARCHAR(191) | 否 | `''` | 条目备注 | +| `done` | BOOLEAN | 否 | `false` | 是否已完成 | +| `doneAt` | DATETIME(3) | **是** | NULL | 勾选时刻;取消勾选置回 NULL | +| `sortOrder` | INT | 否 | `0` | 排序(升序,同值按 id) | +| `createdAt` / `updatedAt` | DATETIME(3) | 否 | — | — | + +索引:`INDEX(listId, sortOrder)`。 + +### 3.5 `CheckListTemplate` — 清单模板 + +| 字段 | 类型 | 空 | 默认 | 说明 | +| --- | --- | --- | --- | --- | +| `id` | INT PK | 否 | AUTO_INCREMENT | 主键 | +| `userId` | INT FK | **是** | NULL | NULL = 系统预置(`builtin=true`) | +| `name` | VARCHAR(191) | 否 | — | 模板名 | +| `scene` | VARCHAR(191) | 否 | `''` | 场景标签 | +| `items` | **JSON** | 否 | — | `[{ title, note }]`,创建清单时展开为 `CheckListItem` | +| `builtin` | BOOLEAN | 否 | `false` | 系统预置:只读、不可删(接口层限制) | +| `createdAt` / `updatedAt` | DATETIME(3) | 否 | — | — | + +索引:`INDEX(userId, builtin)` — 查询条件为 `builtin=true OR userId=me`。 + +系统预置数据(首次访问 `GET /api/checklists/templates` 时**幂等写入**): + +| 模板 | 条目数 | 条目 | +| --- | --- | --- | +| 出差 | 11 | 身份证/证件、手机充电器+充电宝、笔记本电脑+电源、耳机、换洗衣物、洗漱包、常用药、雨伞、车票/机票/酒店确认、工牌/名片、报销单据 | +| 回老家 | 12 | 身份证/证件、钥匙、手机充电器+充电宝、换洗衣物、洗漱包、常用药、给家人的礼物/伴手礼、长辈的药品/营养品、口罩、现金零钱、车票/加油/行程确认、家里门窗水电检查 | + +--- + +## 4. 外键与级联 + +| 子表.字段 | 父表.字段 | ON DELETE | ON UPDATE | +| --- | --- | --- | --- | +| `Habit.userId` | `User.id` | CASCADE | CASCADE | +| `CheckIn.habitId` | `Habit.id` | CASCADE | CASCADE | +| `CheckIn.userId` | `User.id` | CASCADE | CASCADE | +| `CheckList.userId` | `User.id` | CASCADE | CASCADE | +| `CheckListItem.listId` | `CheckList.id` | CASCADE | CASCADE | +| `CheckListTemplate.userId` | `User.id` | CASCADE | CASCADE | + +删除链路:删用户 → 级联清 Habit/CheckIn/CheckList/Template;删 Habit → 清其全部 CheckIn;删 CheckList → 清其全部 Item。业务层删打卡项默认只置 `archived=true`,物理删需 `?hard=1`(此时先 `deleteMany` 记录再删主表)。 + +--- + +## 5. 关键设计决策 + +### 5.1 为什么 `date` 用 `VARCHAR(10)` 而不是 `DATETIME` + +| 维度 | VARCHAR(10) 本地日历日 | DATETIME(UTC) | +| --- | --- | --- | +| 跨零点归属 | 明确:由应用层按 Asia/Shanghai 计算一次,落库即定 | 读时需要再转时区,易错 | +| 范围查询 | `date >= '2026-10-01' AND date <= '2026-10-31'` 直接走索引 | 需函数/边界换算,易失效 | +| 幂等边界 | `UNIQUE(habitId, date, seq)` 天然按「日」收敛 | 需按日截取 | +| 字典序 | 等于时间序,可安全字符串比较 | — | +| 存储 | 10 字节 | 5 字节(略优,但不足以抵消上述成本) | + +代价:跨时区展示需自行换算——本项目全链路 Asia/Shanghai,不成为问题。 + +### 5.2 为什么不冗余 `doneCount` / `streak` + +- **并发**:计数需事务 + 行锁,聚合查询天然一致。 +- **改配置**:用户把「每周 3 次」改成「每周 5 次」,冗余计数立刻失真,需要全量重算;现方案改 `target` 立即生效。 +- **撤销**:撤销即删一条记录,无需维护计数器。 +- **成本**:单个用户打卡项数量在 10~30 量级,月历查询一次拉取整月记录(≤ 数百行)在内存聚合,开销可接受。 + +### 5.3 `seq` 与幂等 + +同日多次打卡用 `seq = max(seq)+1`。唯一约束 `(habitId, date, seq)` 使并发重复提交必然撞 P2002,服务端转 409「该次打卡已记录」,前端幂等收敛,不会出现「点两下加两次」的体验事故。 + +### 5.4 归档 vs 物理删除 + +| 操作 | 语义 | 数据影响 | +| --- | --- | --- | +| `DELETE /api/checkin/habits/:id` | 归档(默认) | `archived=true`,记录全保留 | +| `DELETE /api/checkin/habits/:id?hard=1` | 物理删除 | 先删 CheckIn,再删 Habit | +| `DELETE /api/checklists/:id` | 物理删除(一次性清单,不留历史) | 先删 Item,再删 List | + +理由:打卡是长期行为数据,历史构成 streak 与完成率;清单是短期事务,用完即归档/删除。 + +### 5.5 模板用 JSON 而非明细表 + +模板条目结构固定(`title` + `note`)、只在「创建清单」时整体读取并展开、不需要按条目检索或统计 —— 用 `JSON` 列避免额外的 `TemplateItem` 表与 join。展开后落到 `CheckListItem`,之后模板改动不影响已生成清单。 + +--- + +## 6. 数据量估算 + +单用户规模假设:打卡项 20 个,平均每天打卡 25 次;清单每月 4 份,每份 12 条。 + +| 表 | 年增量(单用户) | 行大小(约) | 年占用(约) | +| --- | --- | --- | --- | +| `CheckIn` | 25 × 365 ≈ 9,100 | ~80 B | ~0.7 MB | +| `CheckListItem` | 4 × 12 × 12 = 576 | ~90 B | ~0.05 MB | +| `Habit` / `CheckList` / `CheckListTemplate` | < 100 | ~200 B | 忽略 | + +结论:**无需分区、无需归档策略**。10 用户 × 5 年 ≈ 45 万行 CheckIn,仍远低于单表瓶颈。若未来接入多租户或做年视图长周期统计,再考虑按月汇总表。 + +--- + +## 7. 典型查询 + +### 7.1 今日待办 + 周期进度 + +```sql +-- 步骤 1: 取未归档打卡项 (走 Habit_userId_archived_sortOrder_idx) +SELECT id, name, period, target, color FROM Habit +WHERE userId = ? AND archived = 0 ORDER BY sortOrder, id; + +-- 步骤 2: 取覆盖所有周期窗口的最小区间内的记录 (走 CheckIn_userId_date_idx) +SELECT habitId, date FROM CheckIn +WHERE userId = ? AND date >= ? AND date <= ?; -- 如 week: '2026-10-19' ~ '2026-10-25' +-- 步骤 3: 内存按 habitId/date 分组 -> 计算 done/percent/streak +``` + +### 7.2 打一次卡 + +```sql +SELECT MAX(seq) FROM CheckIn WHERE habitId = ? AND date = ?; -- -> seq +INSERT INTO CheckIn (habitId, userId, date, seq, note, source) VALUES (?, ?, ?, ?, ?, 'user'); +-- 冲突 -> P2002 -> 409 +``` + +撤销(撤销本周期最近一次): + +```sql +SELECT id FROM CheckIn WHERE habitId = ? AND date BETWEEN ? AND ? +ORDER BY date DESC, seq DESC LIMIT 1; +DELETE FROM CheckIn WHERE id = ?; +``` + +### 7.3 月历(日历视图数据来源) + +```sql +SELECT habitId, date FROM CheckIn +WHERE userId = ? AND date >= '2026-10-01' AND date <= '2026-10-31'; +``` + +按天聚合后与 `Habit.expectedPerDay` 比较得出日状态: + +| 状态 | 条件 | 展示 | +| --- | --- | --- | +| `full` | 全部打卡项当日次数 ≥ 日均期望 | 绿点 | +| `over` | 有任一打卡项当日次数 > 日均期望 | 紫点 | +| `partial` | 有完成但未全完成 | 黄点 | +| `empty` | 无记录或当天无打卡项 | 无点 | + +`Habit.createdAt` 早于该日的才计入期望,避免新建打卡项把历史日期标成「未完成」。 + +### 7.4 清单列表(带进度) + +```sql +SELECT l.*, i.* FROM CheckList l +LEFT JOIN CheckListItem i ON i.listId = l.id +WHERE l.userId = ? AND l.archived = ? +ORDER BY l.updatedAt DESC, i.sortOrder, i.id; +``` + +--- + +## 8. 迁移 + +| 项 | 内容 | +| --- | --- | +| 迁移目录 | `prisma/migrations/20261022120000_add_checkin_checklist/` | +| 内容 | 5 张 `CREATE TABLE` + 6 条 `ADD CONSTRAINT`(见 `migration.sql`) | +| 生成方式 | 手写 SQL(与 `prisma migrate diff` 产出格式一致),已通过 `prisma migrate deploy` 验证 | +| 部署命令 | `npx prisma migrate deploy && npx prisma generate` | +| 回滚 | 无自动回滚。应急:`DROP TABLE CheckIn, CheckListItem, CheckList, CheckListTemplate, Habit;`(注意外键顺序)并把 `_prisma_migrations` 中该记录删除 | + +>V0.2 全为新增表与新增字段(`User` 侧仅新增 Prisma 反向关系,不产生 DDL),对既有数据零影响。 + +--- + +## 9. V0.3 预留 + +| 方向 | 可能的表变更 | +| --- | --- | +| 打卡提醒 | `Habit` 增加 `remindAt TIME`、`remindDays` | +| 打卡统计快照 | 新增 `HabitDailyStat(userId, date, done, expected)` 汇总表,用于年视图/热力图加速 | +| 清单共享协作 | 新增 `CheckListShare(listId, userId, role)` | +| 模板市场 | `CheckListTemplate` 增加 `visibility`、`useCount` | + +以上均可通过「新增表 / 新增可空字段」完成,不影响 V0.2 数据结构。 diff --git a/docs/V0.2/界面设计.html b/docs/V0.2/界面设计.html new file mode 100644 index 0000000..1ace70a --- /dev/null +++ b/docs/V0.2/界面设计.html @@ -0,0 +1,1704 @@ + + + + + +iBoard V0.2 界面设计 · 打卡 / 日历 / 清单 + + + +
+ +
+

iBoard V0.2 · 界面设计

+

打卡 / 日历 / 清单 —— 移动端静态高保真原型(设计基线 420 × 820,适配 375~430px)

+
+ 版本 V0.2(0.2.0)分支 dev-0.2antd-mobile 风格 + 底部 4 Tab(打卡默认)时区 Asia/Shanghai文档日期 2026-10-22 · 修订 2026-10-23 +
+
+ + +

1 设计规范Design Tokens

+

沿用项目既有 :root 变量(src/styles/globals.css),本模块不引入新主题。

+
+
+

语义色

+
+
#1677ff主色 / 今日 / 链接
+
#00b578完成 / 进度条
+
#ffc53d部分完成 / 粒子
+
#a855f7超额完成
+
#ff8f1f连续天数 / 超额徽标
+
#ff3141危险操作
+
#f5f5f5页面底色
+
#f7fbf8已完成卡片底
+
+
+ 日历·全部完成 + 日历·部分完成 + 日历·超额 +
+
+
+

布局与圆角

+
+ 视口420 × 820(375~430 自适应) + NavBar 高45px + TabBar 高72px(含安全区) + 页面内边距左右 12px + 卡片圆角10px + 卡片间距10px 纵向 + 卡片阴影0 1px 2px rgba(0,0,0,.04) + 环形尺寸58 × 58,环宽 6px + 最小点按区≥ 40 × 40 +
+
+
+

字号层级

+
18 / 700日期标题「10月22日」
+
17 / 600NavBar 标题「打卡」
+
16 / 700环形中心数字 / 弹窗标题
+
15 / 600打卡项名 / 清单标题
+
13 / 400正文 / 日历日期数字
+
12 / 400次要信息 / 操作链接
+
11 / 400备注 / 周期范围 / 徽标
+
+
+ + +

2 主界面三个子视图(V0.2 现状;合并方案见 2.12 ②)

+ +
+ + +
+
+ +
+
+
+
10月22日 周四
+
已完成 2 / 4
+
+
+
+ 新建
+
+
+ +
+ +
+
+ + + + +
0/2
+
+
+
喝水
+
每天 2 次连续 12
+
今日
+
+
打卡
+
编辑归档
+
+ + +
+
+ + + + +
1/3
+
+
+
健身
+
每周 3 次连续 5
+
10-19 ~ 10-25
+
+
打卡
+
撤销编辑归档
+
+ + +
+
+ + + + +
+ + + +
+
+
+
读书
+
每月 12 次连续 3
+
10-01 ~ 10-31
+
+
再打一次
+
撤销编辑归档
+
+ + +
+
+ + + + +
+ + + +
+ +1 +
+
+
吃药
+
每天 1 次连续 30
+
今日超额 1
+
+
再打一次
+
撤销编辑归档
+
+
+
+
+
+ 打卡
+
+ 宏观
+
+ 监控
+
+ 我
+
+
+
+ 2.1 打卡视图 · 今天默认进入 +

头部日期 + 完成数;未完成卡片排在前面。环形中心未达标显示 done/target,达标显示对勾(每天 1 次打完即刻闭合);超额在右上角显示 +n。点环或「打卡」按钮均可打卡,卡片底部提供撤销 / 编辑 / 归档。

+
+
+ + +
+
+ +
+
+
打卡
日历
清单
+
+
+ ‹2026年10月› + 回到今天 +
+
+
昨天
今天
明天
+
+ +
+
一二三四五六日
+
+
28
+
29
+
30
+
1
+
2
+
3
+
4
+ +
5
+
6
+
7
+
8
+
9
+
10
+
11
+ +
12
+
13
+
14
+
15
+
16
+
17
+
18
+ +
19
+
20
+
21
+
22
+
23
+
24
+
25
+ +
26
+
27
+
28
+
29
+
30
+
31
+
1
+
+
+ +
本月完成率 82%
+
今天 · 2026-10-22
+ +
+ 喝水 + 每天 2 次 · 当日 2/2 + 已达标 +
+
+ 健身 + 每周 3 次 · 当日 0/1 + 打卡 +
+
+ 读书 + 每月 12 次 · 当日 1/1 + 已达标 +
+
+ 吃药 + 每天 1 次 · 当日 2/1 + 已达标 +
+
+
+
+ 打卡
+
+ 宏观
+
+ 监控
+
+ 我
+
+
+
+ 2.2 日历视图 · 今天选中 +

周一起始月历 + 「昨天 / 今天 / 明天」快捷分段;日期下方状态点:绿=全部完成、黄=部分完成、紫=超额、无点=未完成或未来。今天用主色数字并选中高亮,未来日期数字弱化。下方为该日明细,可直接打卡;底部展示本月完成率。

+
+
+ + +
+
+ +
+
+
打卡
日历
清单
+
+
+
进行中
已归档
模板
+
+ 新建
+
+ +
+
出差 · 杭州出差
+
+
8/12› +
+
+
身份证 / 证件×
+
手机充电器 + 充电宝×
+
笔记本电脑 + 电源×
+
耳机×
+
换洗衣物×
+
洗漱包×
+
常用药×
+
车票 / 机票 / 酒店确认×
+
雨伞×
+
工牌 / 名片×
+
报销单据×
+
现金零钱×
+ +
添加检查项
添加
+
+ 全选全不选复制 + 存为模板归档删除 +
+
+
+ +
+
回老家 · 国庆回老家
+
+
0/12› +
+
+
+
+
+ 打卡
+
+ 宏观
+
+ 监控
+
+ 我
+
+
+
+ 2.3 清单视图 · 展开态 +

卡片头部常显标题、场景标签、进度条与 8/12;点击展开逐条勾选(勾选后绿色圆底、文字加删除线并弱化)。底部可继续添加检查项,并提供全选 / 全不选 / 复制 / 存为模板 / 归档 / 删除。收起态只保留头部一行。

+
+
+ + +
+
+ +
+
+
+
10月22日 周四
+
已完成 2 / 4
+
+
+ 新建
+
+
+
+
+ + + +
0/2
+
+
喝水
每天 2 次
今日
+
打卡
+
编辑归档
+
+
+
+
+
+
新建打卡项
+
例如:喝水 / 健身 / 读书
+
+
每天
每周
每月
每年
+
+
+
−1+
+
+
+
+ + + + +
+
+
补充说明
+
保存
+
取消
+
+
+
+ 打卡
+
+ 宏观
+
+ 监控
+
+ 我
+
+
+
+ 2.4 新建 / 编辑打卡项(底部弹层) +

名称 ≤20 字;周期四选一(每天 / 每周 / 每月 / 每年);目标次数用 Stepper,最小 1;颜色 7 色预设;备注可选。编辑态标题变为「编辑打卡项」并回填当前值。

+
+
+ + +
+
+ +
+
+
10月22日 周四
已完成 3 / 4
+
+ 新建
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + +
+ + + + +
+ +
+
+
吃药
每天 1 次连续 30
今日
+
再打一次
+
撤销编辑归档
+
+
+
+ + + +
1/3
+
+
健身
每周 3 次
10-19 ~ 10-25
+
打卡
+
撤销编辑归档
+
+
+
+
+
+ 打卡
+
+ 宏观
+
+ 监控
+
+ 我
+
+
+
+ 2.5 达标瞬间 · 完成特效 +

环形闭合 + 对勾缩放入场(0.32s);同时 14 颗粒子沿圆周扩散(26~58px,交替主色与金色)+ 主色光环脉冲(0.7s),总时长 ≤900ms,pointer-events:none 不阻塞后续操作;并触发 18ms 轻震动。特效恒开(V0.2 设计变更:头部不再有开关)。

+
+
+ + +
+
+ +
+
打卡
日历
清单
+
‹2026年10月›回到今天
+
昨天
今天
明天
+
+
一二三四五六日
+
+
28
29
30
+
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
27
+
28
29
+
30
31
+
1
+
+
+
本月完成率 82%
+
明天 · 2026-10-23计划(不可打卡)
+
喝水每天 2 次 · 当日 0/2待办
+
健身每周 3 次 · 当日 0/1待办
+
读书每月 12 次 · 当日 0/1待办
+
吃药每天 1 次 · 当日 0/1待办
+
+
+
+ 打卡
+
+ 宏观
+
+ 监控
+
+ 我
+
+
+
+ 2.6 日历 · 明天(计划,只读) +

未来日期不允许打卡:标题右侧显示「计划(不可打卡)」徽标,条目右侧只显示「待办」文案而不渲染按钮,月历上未来日期数字弱化且不打点。昨天视图则相反——可补打最近 7 天内的漏打卡。

+
+
+ + +
+
+ +
+
打卡
日历
清单
+
+
进行中
已归档
模板
+
+ 新建
+
+
+
出差 · 杭州出差
+
12/12›
+
+
+ + + + + + + + +
+
+
+
回老家 · 国庆回老家
+
0/12›
+
+
+
+
+ 打卡
+
+ 宏观
+
+ 监控
+
+ 我
+
+
+
+ 2.7 清单全部完成(12/12) +

进度条 100%、计数变为 12/12、卡片底色转 #f7fbf8,并播放与打卡一致的完成特效。清单完成后用户通常选择「归档」或「复制」复用。

+
+
+ + +
+
+ +
+
+
10月22日 周四
已完成 0 / 0
+
+ 新建
+
+
+
○
+

还没有打卡项

+
创建第一个打卡项
+
+
+
+
+ 打卡
+
+ 宏观
+
+ 监控
+
+ 我
+
+
+
+ 2.8 空态 +

三个视图均具备加载中 / 空态 / 错误(带重试)三态。空态给出明确引导按钮。V0.2 设计变更:打卡页头部不再有「特效开 / 特效关」开关,完成特效与触感反馈恒开,没有任何关闭入口。

+
+
+ + +
+
+ +
+
+
+
报警3
新闻
+
油价
经济数据
策略研究
经济文章
+
+
›
+
+
+
+
央行开展 5000 亿元 MLF 操作,中标利率持平
+
本月 MLF 净投放 2000 亿元,市场流动性保持合理充裕…
+
妙想财经2026-10-22#1
+
+
+
9 月社零同比增长 3.2%,服务消费延续回暖
+
餐饮收入增速回升至 5.1%,可选消费分化明显…
+
妙想财经2026-10-21#2
+
+
+
美联储会议纪要偏鹰,美元指数小幅走强
+
点阵图显示年内或仍有一次降息空间,但分歧加大…
+
妙想财经2026-10-21#3
+
+
+
OPEC+ 维持减产节奏,国际油价日内涨逾 1%
+
布伦特原油重回 78 美元上方,化工链成本端承压…
+
妙想财经2026-10-20#4
+
+
+
+
+
+ 打卡
+
+ 宏观
+
+ 监控
+
+ 我
+
+
+
+ 2.9 宏观 · 原「首页」报警/新闻并入设计变更 +

「首页」Tab 取消,报警与新闻并入宏观。2026-10-23 调整:两者移到最左(报警 → 新闻 → 油价 → 经济数据 → 策略研究 → 经济文章),默认选中「新闻」;共 6 个子 Tab 可横向滚动,当前项底部有 3px 主色指示条;报警数量以红色角标挂在子 Tab 上。tab 条右侧增加渐变遮罩 + 箭头,提示还有未展示的 tab(详见 2.12 ④)。新闻详情为 /macro/news/:id,底部「宏观」保持高亮。

+
+
+ + +
+
+
+
+
C
+
Cheney
+
qian.huang@sunyard.com
+
+
+
前端升级 (OTA)
+
当前版本v0.2.0 · 20261022.1530
+
运行资源内置
+
+
+
开发版本
+
按版本号判新:仅当服务端版本号更高时才提示更新
+
+
+
+
+
检查更新
+
立即升级
+
+
+
+
+
+ 打卡
+
+ 宏观
+
+ 监控
+
+ 我
+
+
+
+ 2.10 我的 · OTA「开发版本」关闭(默认)新功能 +

开关默认关闭 = 按版本号判新:取服务端 latest.json 的 version(如 0.2.0-ota),与当前运行版本按数字字段逐段比较,更高才提示更新。用于生产,避免旧分支被重建(sequence 变大但版本更小)把设备拉回旧版。开关值保存在设备端 localStorage.iboard_ota_dev_mode。

+
+
+ + +
+
+
+
+
C
+
Cheney
+
qian.huang@sunyard.com
+
+
+
前端升级 (OTA)
+
当前版本v0.2.0 · 20261022.1530
+
运行资源内置
+
+
+
开发版本
+
按 build sequence 判新:每次构建都算新版本,便于开发联调
+
+
+
+
新版本v0.2.0-ota (288 KB)
+
+
立即升级
+
回滚
+
+
+
+
+
+ 打卡
+
+ 宏观
+
+ 监控
+
+ 我
+
+
+
+ 2.11 我的 · OTA「开发版本」打开 +

开关打开 = 按 build sequence 判新:走 hotswap 插件的 checkUpdate(),只要服务端 sequence 比本地大就算新版本——每次 CI 构建 sequence 都会变(默认取时间戳),所以任何一次提交都能推到设备上,适合开发联调。切换开关会清空上一次的检查结果,避免上一种口径的结论误导。

+
+
+ +
+ + +

2.12 V0.2.1 界面调整头部统一 / 日历·打卡合并 / 模板分类 / 宏观 tab 溢出

+

本节为 2026-10-23 提出的调整方案。其中 ①头部统一 / ③模板分类 / ④宏观 tab 溢出 已按本节实现; + ②日历与打卡合并 为待确认草案,确认后再改代码。

+ +

① 四个主 Tab 头部统一

+ + + + + + +
页调整前调整后
打卡NavBar 45px,标题「打卡」17px 深色23px,标题「打卡」12px 主色加粗,字距 1px
宏观NavBar 45px,无标题(空白)23px + 标题「宏观」(同打卡规格)
监控NavBar 45px,无标题(空白)23px + 标题「监控」(同打卡规格)
我NavBar 45px 空白条整条去掉,蓝色头像区直接顶到状态栏下方
+

实现为独立组件 PageHeader,只被打卡 / 宏观 / 监控 引用;「我」页不引用,因此三者互不影响。 + 头部背景取页面底色 #f5f5f5(不再是卡片白),视觉上更轻。

+ +
+
+
+ +
+
+
10月23日 周五
已完成 2 / 10
+
+ 新建
+
+
+
+
+ + + +
1/2
+
+
喝水
每天 2 次连续 12
今日
+
打卡
+
撤销编辑归档
+
+
+
+ + + +
0/1
+
+
俯卧撑
每周 1 次
10-19 ~ 10-25
+
打卡
+
编辑归档
+
+
+
+
+
打卡
+
宏观
+
监控
+
我
+
+
+
+ ①-a 打卡(头部已缩至 23px)已实现 +

原 45px 白色 NavBar 换成 23px 底色条 + 主色标题,页面上移,首屏多出一张卡片的高度。

+
+
+ +
+
+ +
+
+
+
报警1
新闻
油价
+
经济数据
策略研究
经济文章
+
+
›
+
+
+
+
央行开展 5000 亿元 MLF 操作,中标利率持平
+
本月 MLF 净投放 2000 亿元,市场流动性保持合理充裕…
+
妙想财经2026-10-22#1
+
+
+
9 月社零同比增长 3.2%,服务消费延续回暖
+
餐饮收入增速回升至 5.1%,可选消费分化明显…
+
妙想财经2026-10-21#2
+
+
+
+
+
打卡
+
宏观
+
监控
+
我
+
+
+
+ ①-b + ④ 宏观:头部标题化 · 报警/新闻置最左 · 右侧溢出提示已实现 +

顺序:报警 → 新闻 → 油价 → 经济数据 → 策略研究 → 经济文章,默认选中「新闻」(/macro 自动跳 /macro/news)。
+ 溢出提示:tab 条仍可向右滚动时,右边缘出现白色渐变遮罩 + 主色圆底箭头「›」(1.5s 轻微左右摆动,可点击向右翻页),滚到末尾自动消失;切换 tab 时自动把激活项滚到可视区中间。
+ antd-mobile Tabs 自带的右侧渐变遮罩只有 30px 且无箭头,用户难以察觉,故叠加此提示层。

+
+
+
+ +

② 打卡:日历 + 打卡合并(待确认草案)

+

把原来的「打卡」「日历」两个 tab 合成一个:上半部日历,下半部打卡卡片。取舍按下述规则:

+ + + + + + + + + + +
项规则
页面结构SwipeTabs 从 3 个减为 2 个:日历(含打卡) / 清单
上方日历保留月历网格 + 月份左右切换 + 「回到今天」;去掉「昨天 / 今天 / 明天」分段控件(点日期即可,节省 38px 高度)
日历高度日期格子 38px → 31px,去掉 6 行补位中的空行;整块约 240px
下方打卡沿用现有打卡卡片样式,但紧凑化:内边距 12px → 8px,环形 58px → 42px(环宽 6 → 5),行高压缩,卡片间距 10 → 7px
卡片内容保留环形进度(含达标对勾 / 超额 +n)、名称、周期与目标、连续天数、打卡按钮;撤销 / 编辑 / 归档收进「⋯」,减少一行高度
分组按周期类型分组:每天 → 每周 → 每月,每组一个小标题(左侧主色竖条 + 组名 + 条数);去掉「每年」分组(已有年目标项并入「每月」区末尾的「其他周期」小标题,或建议直接隐藏年视图)
联动点日历某日 → 下方卡片即该日数据(进度仍按该日所属周期算);未来日期只读,按钮变灰显示「计划」
空态日历下方直接显示「该日没有打卡项」+「+ 新建」引导
+ +
+
+
+ +
+
+
日历
清单
+
+ +
+
+ ‹2026年10月› + 回到今天 +
+
一二三四五六日
+
+
29
30
+
1
2
+
3
4
5
+
6
7
+
8
9
+
10
11
12
+
13
14
15
+
16
17
18
19
+
20
21
+
22
23
24
25
+
26
27
28
+
29
30
31
1
+
+
本月完成率 82%今天 · 2026-10-22
+
+ +
今天已完成 2 / 4点环即打卡
+ +
+
每天2 项
+ +
+
+ + + + +
1/2
+
+
+
喝水
+
每天 2 次 · 今日 1/2连续 12
+
+
打卡
+
⋯
+
+ +
+
+ + + + +
+ +1 +
+
+
吃药
+
每天 1 次 · 今日 2/1连续 30
+
+
再打一次
+
⋯
+
+ +
每周1 项
+
+
+ + + + +
1/3
+
+
+
健身
+
每周 3 次 · 本周 1/3连续 5
+
+
打卡
+
⋯
+
+ +
每月1 项
+
+
+ + + + +
0/12
+
+
+
读书
+
每月 12 次 · 本月 0/12
+
+
打卡
+
⋯
+
+
+
+
+
打卡
+
宏观
+
监控
+
我
+
+
+
+ ②-a 合并后的主视图(日历在上 · 打卡在下,按天 / 周 / 月分组)待确认 +

卡片整体压缩:环形 42px、内边距 8px、行距 7px,单卡高度约 58px(原 92px),一屏可看 4~5 项。分组顺序固定为 每天 → 每周 → 每月, + 组标题左侧 3px 主色竖条;无「每年」分组。日历顶部的「昨天 / 今天 / 明天」分段已去掉,直接点日期切换,选中日靠「今天」主色数字与蓝色实心块识别。

+
+
+ +
+
+ +
+
日历
清单
+
+
‹2026年10月›回到今天
+
一二三四五六日
+
+
29
30
+
1
2
+
3
4
5
+
6
7
+
8
9
+
10
11
12
+
13
14
15
+
16
17
18
19
+
20
21
+
22
23
24
25
+
26
27
28
+
29
30
31
1
+
+
本月完成率 82%明天 · 2026-10-23
+
+
明天计划(不可打卡)
+
+
每天2 项
+
+
+ + + +
0/2
+
+
喝水
每天 2 次 · 当日 0/2
+
计划
⋯
+
+
+
+ + + +
0/1
+
+
吃药
每天 1 次 · 当日 0/1
+
计划
⋯
+
+
每周1 项
+
+
+ + + +
1/3
+
+
健身
每周 3 次 · 本周 1/3
+
计划
⋯
+
+
+
+
+
打卡
+
宏观
+
监控
+
我
+
+
+
+ ②-b 未来日期(计划态) +

选中未来日期时,打卡按钮统一变灰显示「计划」,点击不生效;环形仍显示该日所属周期的既有进度(此处举例为周期初)。 + 补打窗口仍是最近 7 天,超出时同样只读。

+
+
+ +
+
+ +
+
日历
清单
+
+
‹2026年10月›回到今天
+
一二三四五六日
+
+
29
30
+
1
2
+
3
4
5
+
6
7
+
8
9
+
10
11
12
+
13
14
15
+
16
17
18
19
+
20
21
+
22
23
24
25
+
26
27
28
+
29
30
31
1
+
+
本月完成率 82%今天 · 2026-10-22
+
+
今天已完成 2 / 4点环即打卡
+
+
每天2 项
+
+
+ + + +
1/2
+
+
喝水
每天 2 次 · 今日 1/2
+
打卡
⋯
+
+
+
+ + + + +
+ +1 +
+
吃药
每天 1 次 · 今日 2/1
+
再打一次
⋯
+
+
+
+
+
+
吃药 · 每天 1 次
+
撤销上次打卡今日 2 次
+
编辑打卡项名称 / 周期 / 目标
+
归档历史记录保留
+
取消
+
+
+
打卡
+
宏观
+
监控
+
我
+
+
+
+ ②-c 卡片「⋯」操作面板 +

紧凑化后卡片不再常显三个文字链接,改由右上角「⋯」唤出底部面板:撤销 / 编辑 / 归档。面板项 44px 高, + 「归档」用危险色;撤销仅在当日有记录时可用,否则置灰。

+
+
+
+ +

③ 清单:新增「模板」分类(与进行中 / 已归档并列)已实现

+

清单页分段从 2 个变为 3 个:进行中 / 已归档 / 模板。模板拥有独立的增删改查,按名称字母顺序(中文按拼音、数字按数值)排序; + 新建清单时只保留名称,去掉场景,模板改为点击后在右侧滑出的面板里选择。

+ +
+
+
+ +
+
日历
清单
+
+
进行中
已归档
模板
+
+ 新建模板
+
+ +
+
出差预置11 项
+
+ 身份证 / 证件手机充电器 + 充电宝 + 笔记本电脑 + 电源耳机+7 +
+
由此新建
+
+ +
+
回老家预置12 项
+
+ 身份证 / 证件钥匙 + 手机充电器 + 充电宝换洗衣物+8 +
+
由此新建
+
+ +
+
露营装备2 项
+
帐篷睡袋
+
由此新建编辑删除
+
+
+
+
打卡
+
宏观
+
监控
+
我
+
+
+
+ ③-a 模板分类已实现 +

模板卡片:名称 + 「预置」标记 + 项数;下方最多展示 4 个检查项摘要(超出折叠为 +n);底部操作「由此新建 / 编辑 / 删除」。 + 系统预置模板不可编辑、不可删除(只保留「由此新建」)。右上角按钮随分段切换:模板页为「+ 新建模板」,其余为「+ 新建」。

+
+
+ +
+
+ +
+
日历
清单
+
+
+
新建检查清单
+
周末露营
+
+ +
露营装备2 项 ›
+
+
创建
+
取消
+
+
+
选择模板×
+
按名称字母顺序排列
+
不使用模板空白清单
+
出差预置11 项
+
回老家预置12 项
+
露营装备2 项
+
+
+
+
打卡
+
宏观
+
监控
+
我
+
+
+
+ ③-b 新建清单 + 模板右侧滑出面板已实现 +

表单只剩两项:名称(≤20 字,必填)与 模板(可空)。场景选择器已移除——场景不再由用户填写,新建时沿用所选模板自带的场景(无模板则为空),历史数据仍正常展示场景标签。
+ 点模板行从右侧滑出 78% 宽面板:首项固定为「不使用模板」,其后按字母顺序列出全部模板(预置带标记、右侧显示项数),选中项主色高亮,点遮罩或 × 关闭。

+
+
+ +
+
+ +
+
日历
清单
+
+
+
编辑模板
+
露营装备
+
+ +
+
1
帐篷
×
+
2
睡袋
×
+
3
检查项内容
×
+
+
+ 添加检查项
+
+
保存
+
取消
+
+
+
+
打卡
+
宏观
+
监控
+
我
+
+
+
+ ③-c 模板编辑(增删改)已实现 +

新建 / 编辑共用同一弹层:模板名称(≤20 字)+ 检查项列表(逐条 ≤40 字,可删除、可追加)。保存时至少需 1 条有效检查项,名称为空或全空则拦截并 Toast 提示。 + 删除模板需二次确认,且不影响已用该模板创建过的清单。

+
+
+
+ +

④ 补充:模板接口

+ + + + + + + +
方法路径说明
GET/api/checklists/templates模板列表(含预置),服务端按名称字母顺序排序
GET/api/checklists/templates/:id模板详情
POST/api/checklists/templates新建模板 { name, items[] }
PUT/api/checklists/templates/:id改名称 / 检查项(预置模板拒绝)
DELETE/api/checklists/templates/:id删自建模板(预置模板拒绝)
+

排序口径:Intl.Collator('zh-Hans-CN', { numeric: true, sensitivity: 'base' }),中文按拼音、数字按数值比较,前后端一致。

+ + +

3 组件状态定义Component States

+ +

3.0 页面头部(PageHeader)

+ + + + + + + +
项取值
高度23px(原 NavBar 45px 的一半),flex: 0 0 23px 不参与压缩
标题字号 / 字重12px / 600
标题颜色主色 #1677ff
背景页面底色 #f5f5f5
适用范围打卡 / 宏观 / 监控;「我」页不使用
+

实现为独立组件 src/components/PageHeader.jsx,三页各自引用、互不影响;「我」页移除 NavBar 后头像区直接贴顶。

+ +

3.1 环形进度(尺寸 58,环宽 6,r=26,周长 163.4)

+ + + + + + + +
状态条件环形中心卡片底按钮
未开始done = 0仅灰底环0/2白打卡(主色)
进行中0 < done < target按 done/target 填充(45°…330°)1/3白打卡(主色)
已达标done = target闭合整圈对勾(主色)#f7fbf8再打一次(灰)
超额done > target闭合 + 右上角 +n 橙标对勾#f7fbf8再打一次(灰)
每日 1 次达标target = 1 且已打卡直接整圈闭合,不做渐增生长期对勾#f7fbf8再打一次
+

环形进度 = 周期完成度(done / n);日历日标记 = 当日份额(当日次数 / 日均期望),两者口径不同,互不影响。

+ +

3.2 日历日期

+ + + + + + + + + +
状态判定视觉
全部完成 full所有打卡项当日次数 ≥ 日均期望,且无超额绿色圆点
超额 over全部达标且存在任一打卡项当日次数 > 期望紫色圆点
部分完成 partial有完成但未全部完成黄色圆点
无 empty无记录 / 当天无打卡项 / 未来日期不显示圆点
今天date = today数字主色加粗
选中用户点击主色实心底,数字与圆点转白
非本月 / 未来月份不符 / date > today数字弱化(#c8c8c8 / #b0b0b0)
+ +

3.3 清单检查项

+ + + + + +
状态勾选框文字触发
未完成1.5px 灰边圆圈,对勾透明#333点击圆圈或文字切换
已完成绿底绿边,白色对勾显现(0.2s)#a0a0a0 + 删除线同上,写 doneAt
整单完成—进度条 100%、卡片转绿最后一项勾选时播放特效
+ + +

4 交互与动效Motion

+ + + + + + + + + +
动效时长曲线 / 实现说明
环形增长450mscubic-bezier(.4,0,.2,1),stroke-dashoffset 过渡12 点方向顺时针增长
对勾入场320msscale(.4) → 1 + 透明度达标瞬间出现
完成粒子≤900ms14 颗粒子按角度 --a、距离 --d 扩散,交错 40ms 延迟不阻塞点击
光环脉冲700ms主色圆环放大淡出与粒子同时开始
触感反馈18msnavigator.vibrate(18)WebView 不支持则静默跳过
进度条填充400mswidth 过渡清单勾选后更新
视图切换—SwipeTabs 跟手横滑与既有模块一致
+ +

4.1 打卡时序

+
用户点环 / 点「打卡」 + └─ 前端乐观更新:done+1,重算 percent / completed / over,环形立即增长 + └─ POST /api/checkin/habits/:id/check + ├─ 200 → 用服务端 item 覆盖本地;若 completed → 触发特效 + 震动 + ├─ 400 → 回滚 + Toast(未来日期 / 超出 7 天补打窗口) + ├─ 409 → 视为已打卡,刷新一次(幂等) + └─ 网络失败 → 回滚 + Toast「打卡失败」
+ + +

5 适配与可达性

+ + + + + + + +
项约定
视口设计稿 420×820;375~430px 内元素不换行、不裁切;底部 TabBar 4 项每项 ≥ 44px 可点宽度
安全区TabBar 使用 antd-mobile safeArea,页面底部留出 tabbar-h + safe-bottom
动效特效恒开(V0.2 设计变更,无开关):单次 ≤900ms、pointer-events:none,不阻塞连续打卡;不再响应 prefers-reduced-motion
大字号卡片采用 grid 自适应列宽,文字超长换行不截断;名称限制 20 字、检查项 40 字
深色模式V0.2 沿用现有浅色主题,暂不适配
+ +

本文档为静态设计稿,不含脚本逻辑;实现对应 src/pages/checkin/*.jsx、src/components/RingProgress.jsx 与样式段(src/styles/globals.css)。接口与数据口径见《设计文档.md》与《数据库设计文档.md》。

+ +
+ + diff --git a/docs/V0.2/离线编辑模块/0.2.1实施方案.md b/docs/V0.2/离线编辑模块/0.2.1实施方案.md new file mode 100644 index 0000000..7229d34 --- /dev/null +++ b/docs/V0.2/离线编辑模块/0.2.1实施方案.md @@ -0,0 +1,103 @@ +# 0.2.1 实施方案:PostgreSQL 迁移 + 任务模块离线编辑 (PowerSync) + +| 项 | 内容 | +| --- | --- | +| 分支 | `dev-0.2.1`(worktree `iboard-dev-0.2.1`,独立临时分支,与 master / dev-0.2 隔离) | +| 日期 | 2026-10-25 | +| 依据 | [PowerSync可行性研究报告](./PowerSync可行性研究报告.md),按其 5.2.2 路线执行 | +| 目标 | 在现网应用与数据不受影响的前提下,验证任务模块离线编辑 + 联网后自动双向同步;数据库由 MariaDB 迁移至 PostgreSQL | + +## 1. 已确认决策(2026-10-25 与需求方对齐) + +| 决策点 | 结论 | +| --- | --- | +| 技术路线 | PowerSync 全链路(自部署 powersync-service) | +| 离线范围 | 仅任务模块(Project / ProjectMember / BoardColumn / Task / TaskMember / TaskTag);打卡、清单本阶段不纳入;新闻/指标/监控等时序域仅做普通缓存 | +| 客户端 | 先 Web / Windows 外壳(WebView2)跑通全链路;Android 真机后续验证 | +| 编号 | 项目短前缀由用户自拟(2~8 位,全局唯一);任务号 = 项目内自增整数,展示 `前缀-序号`;编号由服务端统一分配,离线创建的任务在同步定号前显示临时标识 | +| 初始数据 | 从现网 MariaDB 只读快照导出并转换导入新 PG;0.2.1 期间产生的新数据不回写现网 | +| 端口 | Web/API 13001、PowerSync WebSocket 13002、PG 仅容器内网(调试绑 127.0.0.1:15432) | +| 部署 | 源码上传 baihu `/app/iboard-0.2.1`,服务器上 `docker compose build`;不使用原 CI/CD | +| 现网保护 | `/app/iboard`、宿主 3306 MySQL、3001 端口、原 CI/CD 一律不使用(快照导出仅 SELECT) | +| 磁盘 | 允许在 baihu 执行 docker 悬空镜像/构建缓存的安全清理(磁盘仅剩 9.9G) | + +## 2. 目标架构 + +``` +[Web / Tauri WebView2] + React + @powersync/react + 本地 WASM SQLite (OPFS 持久化) + │ 离线: 直接读写本地库; 写操作进入 upload queue + │ 联网: 批量 POST 上行到 Express API (按 uuid 幂等 upsert) + ▼ +[iboard-0.2.1 Express :13001] ── Prisma (adapter-pg) ──┐ + + /api/powersync/token (为登录用户签发同步 JWT) ▼ + [PostgreSQL 容器] (wal_level=logical) + ▲ 逻辑复制 (publication + replication slot) +[powersync-service :13002] ── Sync Rules: 任务域 6 表 + User 只读引用, 按 ProjectMember 过滤 +``` + +- 写路径:客户端本地写 → upload queue → 上行 API → Prisma 写 PG → 逻辑复制捕获 → 广播各端; +- 读路径:任务模块页面全部改读本地 SQLite(@powersync/react 响应式查询),其余页面维持 axios REST; +- 认证:复用现有 jsonwebtoken 体系,新增 `/api/powersync/token` 签发 PowerSync 校验的 JWT。 + +## 3. 数据模型改造(原则:只增不改) + +采用报告 3.2 中的「新增 uuid 列 + 应用层幂等 upsert」选项,不做全表主键 UUID 化:外键关系全部保留,存量数据迁移风险最小。 + +- 任务域 6 表各加 `uuid varchar(36) unique`(客户端创建时生成;存量行导入时回填); +- `Project` 加 `code varchar(8) unique`(用户自拟短前缀;存量项目导入时回填自动码,后续可改); +- `Task` 加 `number int`,`@@unique([projectId, number])`(服务端按项目 max+1 分配;离线创建的任务上行时定号,本地未定号行显示临时标识); +- 主键仍为自增 Int,业务外键不动;客户端上行一律以 uuid 引用父子行,服务端解析为内部 id; +- Prisma datasource: `mysql` → `postgresql`;本分支重建基线迁移(旧 MySQL 迁移文件移除,历史保留在 git)。 + +### 3.1 Sequential ID Mapping(落地时按官方模式补充) + +按 PowerSync 官方 [Sequential ID Mapping](https://docs.powersync.com/client-sdks/advanced/sequential-id-mapping) 模式实施,解决"离线新建行同步回填后本地 id 变化"问题: + +- 任务域 6 表再加**外键 uuid 镜像列**(`Task.projectUuid/columnUuid/parentUuid`、`BoardColumn.projectUuid`、`ProjectMember.projectUuid`、`TaskMember/TaskTag.taskUuid`),由 **PG 触发器**在写入时按 int 外键自动回填(迁移 `20261026010000_uuid_fk_mapping`),库级 NOT NULL; +- Sync Streams 以 **`uuid` 作为客户端主键**(`SELECT "uuid" AS "id"`),镜像列别名输出回 `projectId/columnId/parentId/taskId` —— 客户端 schema 与页面代码**零改动**; +- 服务端 upload 处理器按 uuid 解析父行(`findUnique({ where: { uuid } })`),解析后再写 int 外键 → 触发器回填镜像,两侧闭环一致; +- 镜像列在 Prisma 模型中声明为可空(触发器保证库级非空),避免 create 校验拦截。 + +## 4. 同步与冲突 + +- Sync Rules 仅下发任务域 6 表 + `User`(展示名称/头像用),按 `ProjectMember.userId = auth.parameters.userId` 过滤项目可见性;时序/监控/米家等表不纳入; +- 上行:`POST /api/powersync/upload` 批处理客户端 CRUD 操作,按 uuid 幂等 upsert,更新时以服务端时间戳做 LWW 仲裁; +- 冲突:字段级 LWW 为主;`BoardColumn.sortOrder` / `Task.sortOrder` 由服务端重排仲裁;并发唯一冲突(项目前缀、看板列名)返回明确错误,客户端提示重试; +- `Task.number` / `Project.code` 由服务端唯一分配,多端并发下天然无冲突。 + +## 5. 部署拓扑(baihu `/app/iboard-0.2.1`) + +docker compose 三服务: + +| 服务 | 镜像 | 端口 | 说明 | +| --- | --- | --- | --- | +| `pg-021` | postgres:16-alpine | 仅容器内网 | `wal_level=logical`;数据卷 `./data/pg` | +| `powersync-021` | powersync 官方镜像 | 13002 对外 | 连接 PG 逻辑复制 + WebSocket 下发 | +| `app-021` | 本仓库 Dockerfile 构建 | 13001 对外 | `OTA_PUBLIC_BASE=http://bh.vps.honor3:13001` | + +- 环境变量由 compose 注入:`DATABASE_URL`(指向 pg-021)、`JWT_SECRET`(0.2.1 独立新设)、`PORT=13001`; +- 0.2.1 容器内 cron/米家调度预计无凭证或产生重复外呼,部署时以环境开关关闭(实现于 M4)。 + +## 6. 数据迁移(一次性) + +1. 在 baihu 上用仓库自带 `mariadb` 驱动以只读 SQL 从宿主 MySQL(3306) 导出全表 JSON(仅 SELECT); +2. 转换:回填 uuid / `Project.code`(按名称自动生成,可后改)/ `Task.number`(按项目内 createdAt 顺序);时间字段转 PG 时间戳; +3. PG 侧先 `prisma migrate deploy` 建基线表,再执行 ETL 写入; +4. 校验:逐表行数比对 + 抽样字段比对,输出核对报告。 + +## 7. 里程碑 + +1. **M1** schema/适配层切 PG(本提交):schema 字段、db.js 适配器、基线迁移重建、本地 `prisma generate` 通过; +2. **M2** 服务端:powersync token 端点、upload 幂等上行端点、任务域路由幂等化与编号分配逻辑; +3. **M3** 前端:任务模块数据层切 PowerSync(本地库 + useQuery + 上传队列),桌面端联调; +4. **M4** compose 编排 + ETL 导数 + baihu 部署,端口/现网隔离验证; +5. **M5** 离线-联网闭环验证(断网编辑 → 恢复自动同步 → 多端一致),Android 真机评估。 + +## 8. 开放问题 / 风险 + +- powersync-service 的元数据存储(bucket storage)对 MongoDB 的依赖需在部署阶段核实;若当前版本不支持用 PG 存元数据,则加一个 mongo 容器; +- Android System WebView 的 OPFS/WASM SQLite 兼容性(M5 评估,先桌面); +- 项目前缀全局唯一在多端并发下的冲突提示体验(上行返回结构化错误码); +- 0.2.1 验证期数据与现网彻底隔离,不回迁;若验证后需并回主线,另行决策迁移口径。 diff --git a/docs/V0.2/离线编辑模块/PowerSync可行性研究报告.md b/docs/V0.2/离线编辑模块/PowerSync可行性研究报告.md new file mode 100644 index 0000000..17c4754 --- /dev/null +++ b/docs/V0.2/离线编辑模块/PowerSync可行性研究报告.md @@ -0,0 +1,127 @@ +# 基于 PowerSync 的离线编辑可行性研究报告 + +| 项 | 内容 | +| --- | --- | +| 版本 | V0.2 | +| 日期 | 2026-10-25 | +| 评估对象 | PowerSync(powersync.com,开源 self-hosted + 云服务) | +| 评估目标 | 项目离线操作(本地编辑)+ 联网后自动同步 | +| 结论(TL;DR) | **技术上可行,但落地成本高、风险中高。不建议全量接入;若确需离线,建议仅对「打卡 / 清单 / 任务」等用户编辑域做局部接入,且需要先解决 MariaDB 兼容与主键策略两个前置问题。** | + +--- + +## 1. 项目现状约束(评估输入) + +结合代码库(`package.json`、`prisma/schema.prisma`、`server/index.js`)梳理: + +1. **前端**:React 18 SPA(antd-mobile),通过 axios 调 `/api/*` REST 接口;`src/api`、`src/hooks`、`src/pages` 均以"请求 → state → 渲染"模式组织,无任何本地持久化数据层。 +2. **桌面/移动端**:Tauri 2(Windows WebView2 桌面 + Android System WebView),意味着前端运行在 WebView 内,浏览器类 SDK 是候选。 +3. **后端**:Express + Prisma,数据库为 **MariaDB / MySQL**(`provider = "mysql"`)。 +4. **认证**:自签 JWT(jsonwebtoken),登录接口在 `/api/auth`。 +5. **写路径**:全部业务写操作走 REST(打卡、清单、任务模块);同时存在大量**服务端定时写入**(cron:新闻、指标、监控、米家采样等,见 `server/cron.js`)。 +6. **数据模型**:约 25 张表,主键全部为 `Int @id @default(autoincrement())` 自增整数,多表带复合唯一约束(如 `CheckIn @@unique([habitId, date, seq])`)。 +7. **数据体量特征**:两类数据并存—— + - **用户编辑域**(Habit / CheckIn / CheckList* / 任务模块 10 张表):冲突敏感、离线价值高; + - **服务端只读时序域**(News / Indicator / ServerNodeHistory / MihomeEnv* 等):append-only、量大、离线价值中等(缓存即可满足)。 + +--- + +## 2. PowerSync 是什么、怎么工作 + +- 架构:客户端本地 **SQLite**(Web 端为 WASM SQLite,存 OPFS/IndexedDB)↔ **PowerSync Service**(self-hosted 开源组件或云服务)↔ 源数据库。服务端通过 **逻辑复制/变更流** 捕获源库写入,按 **Sync Rules** 定义的下推子集同步到每个客户端的本地 SQLite。 +- 写路径(官方推荐模式):客户端离线写本地 SQLite → 进入 **upload queue** → 联网后由前端把变更批量 POST 到**现有后端 API** → 后端用原有方式(本项目即 Prisma)写库 → 变更流捕获 → 广播回各端。 +- 认证:客户端持后端签发的 JWT 连接同步服务;PowerSync 只做校验,不签发(可复用现有 jsonwebtoken 体系)。 +- 服务端直写的表(cron 产生)也能被变更流捕获并下推,不需要改造写入方。 + +参考:[PowerSync Service(GitHub)](https://github.com/powersync-ja/powersync-service)、[源数据库支持](https://docs.powersync.com/configuration/source-db/setup)。 + +--- + +## 3. 逐项可行性分析 + +### 3.1 数据库兼容性 —— ⚠️ 主要风险点 + +- PowerSync 对源库支持:**PostgreSQL(GA)**、MongoDB、SQL Server;**MySQL 仍处于 Beta**(基于 binlog 复制,见 [MySQL Beta 公告](https://releases.powersync.com/announcements/mysql-backend-database-support-has-reached-beta)、[Alpha 公告](https://releases.powersync.com/announcements/announcing-mysql-as-a-source-database-alpha))。 +- 本项目用的是 **MariaDB**。MariaDB 的 binlog 格式与 MySQL 8 有差异(GTID、行事件细节),PowerSync MySQL 插件(@powersync/mysql-zongji 系)对 MariaDB **没有官方兼容承诺,需要实测验证**,很可能踩坑。 +- **结论**:要么实测 MariaDB + MySQL-beta 通路(风险高),要么把数据库迁到 PostgreSQL(PowerSync 最成熟路径,Prisma 迁移成本可控但涉及全部表和线上数据)。这是"能不能用"的第一道门槛。 + +### 3.2 数据模型适配 —— 改造成本大 + +- PowerSync Sync Rules 要求同步表有稳定的行标识,**强烈建议 UUID 文本主键**;而本项目全部是自增整数主键,且客户端离线创建记录时无法预知自增 id。 +- 离线创建的通用解法:主键改为客户端生成的 UUID(`char(36)`),或新增 `uuid` 列 + 应用层幂等 upsert。对用户编辑域的 10 余张表意味着**一次不小的 schema 迁移 + 全部相关路由改造**(迁移原则"只增不改"仍可遵守,但外键列类型变更工作量大)。 +- `DateTime`、`Json` 类型可映射(客户端以 ISO 文本/JSON 文本存储),但 `BigInt`(监控表)在 JS 端需按文本处理,注意精度。 +- 复合唯一约束(`CheckIn(habitId,date,seq)`、`BoardColumn(projectId,name)` 等)在离线多端并发下会冲突,需在 upload 处理端路由里做服务端仲裁(如 seq 重排、重名加后缀),现有路由大多不具备幂等/仲裁语义。 + +### 3.3 写路径(离线编辑 + 上行同步)—— 模式契合,但需逐路由改造 + +- PowerSync 的 upload-queue → 后端 API 模式与现有 Express 架构**天然契合**:不需要后端暴露直接写库的通口,各业务路由补成"接收客户端 UUID + 幂等 upsert"即可。 +- 打卡/清单/任务的路由数量约 3 个 router 文件(checkin / checklists / projects / tasks),逐个改造为幂等是可控的。 +- 服务端 cron 写入的表(新闻/指标/监控/米家)无需改造写入方,只需在 Sync Rules 里定义是否下推。 + +### 3.4 读路径与前端数据层 —— 全面前重写 + +这是**工作量最大**的部分: + +- 现有 `src/api` + axios + useEffect 模式必须替换为 PowerSync 本地 SQLite 查询 + `@powersync/react` 的响应式 hooks(useQuery 等),否则离线读不到数据。 +- 涉及 `src/pages` 下全部用户编辑域页面(checkin、task),以及若要离线浏览还需覆盖 news/macro/monitor 的只读缓存页。 +- 工作量量级:相当于把前端数据层重构一遍;可与"离线只读缓存(HTTP 缓存)"分层实施以降低风险。 + +### 3.5 客户端运行环境 —— 大体可行,Android 需验证 + +- **浏览器 / Tauri 桌面(WebView2)**:PowerSync Web SDK(WASM SQLite + OPFS)在 Chromium 系 WebView 已被官方支持,可行。 +- **Tauri Android(System WebView)**:WASM SQLite 性能与 OPFS 在部分国产/旧版 WebView 上存在兼容性风险,需真机验证;备选方案是封装原生 SQLite(官方 Capacitor 插件思路可借鉴,但 Tauri 无官方插件,需自研 Rust 命令桥接,成本额外增加)。 +- 时序大表(ServerNodeHistory 每分钟一条、MihomeEnv*)不建议纳入同步——Sync Rules 需做时间窗过滤(如仅近 7 天),否则客户端存储和同步流量都会失控。 + +### 3.6 认证与部署 —— 可行 + +- JWT 可复用:新增一个 `/api/powersync/token` 端点为已登录用户签发 PowerSync 接受的 token,改造小。 +- 部署:self-hosted 需在现有 Express 之外多跑一个 powersync-service 进程(含 Mongo 存储元数据或云实例),并暴露 WebSocket 端口;或使用 PowerSync Cloud。运维面增加一个组件。 + +### 3.7 冲突解决 + +- PowerSync 本身不做自动合并,只提供上传队列与冲突回调;对"最后写入胜出"友好的字段(清单勾选、任务标题/状态)默认策略够用;对 `CheckIn.seq`、`BoardColumn.sortOrder` 这类"位置/序号型"数据需要自定义仲裁逻辑(上传端或同步流中处理),设计上可解,但要专门做。 + +--- + +## 4. 风险与成本汇总 + +| # | 风险/成本项 | 等级 | 说明 | +| --- | --- | --- | --- | +| 1 | MariaDB 兼容性未验证,MySQL 支持仅 Beta | 高 | 很可能需要迁移 PostgreSQL | +| 2 | 前端数据层全面重构(axios → 本地 SQLite 查询) | 高 | 涉及所有相关页面 | +| 3 | 用户编辑域主键整数自增 → UUID 迁移 | 中高 | schema + 路由幂等改造 | +| 4 | 复合唯一约束的离线冲突仲裁 | 中 | CheckIn.seq、列名、排序等 | +| 5 | Android WebView WASM SQLite/OPFS 兼容 | 中 | 需真机验证 | +| 6 | 新增 self-hosted 组件的运维 | 中 | 多一个常驻服务 | +| 7 | 时序大表同步范围控制 | 低 | Sync Rules 时间窗过滤即可 | + +对照收益:真正需要离线**编辑**的只有打卡 / 清单 / 任务三个域(数据量小、单用户为主、冲突场景有限);其余域离线**只读缓存**即可满足。 + +--- + +## 5. 结论与建议 + +### 5.1 结论 + +**可行,但不建议以"全项目接入 PowerSync"的方式落地。** PowerSync 的同步模型与项目的 Express API 写路径契合、认证可复用、桌面端可行;但 MariaDB/MySQL-beta 兼容、整数自增主键、前端数据层全面重构三座大山使总成本明显高于当前阶段的收益。 + +### 5.2 建议(按优先序) + +1. **首选(低成本方案)**:不引入 PowerSync,为用户编辑域实现轻量离线队列——前端把写请求落到 localStorage/IndexedDB 待发队列(含客户端生成的 UUID),联网后重放到现有 REST API(路由补幂等 upsert);只读页用 HTTP 缓存 + SW。可覆盖约 80% 的离线诉求,改动局限在 `src/api` 层与少量路由。 +2. **若决定采用 PowerSync**: + - 前置 1:源库迁移 PostgreSQL(避开 MySQL Beta + MariaDB 兼容雷区); + - 前置 2:仅将用户编辑域(Habit/CheckIn/CheckList*/任务模块)纳入 Sync Rules,时序与监控表排除或做时间窗过滤; + - 前置 3:这些表的主键/唯一标识改造为客户端可生成的 UUID; + - 分阶段:先桌面端(WebView2)验证全链路,再攻 Android WebView 兼容性; + - 冲突策略:字段级 LWW 为主,seq/sortOrder 类服务端仲裁。 +3. **不建议**:为离线只读域(新闻/指标/监控)上 PowerSync——用简单缓存即可,同步引擎属于过度设计。 + +--- + +## 6. 参考资料 + +- [PowerSync Service(GitHub,self-hosted 开源)](https://github.com/powersync-ja/powersync-service) +- [PowerSync 源数据库配置文档](https://docs.powersync.com/configuration/source-db/setup) +- [MySQL 后端支持 Beta 公告](https://releases.powersync.com/announcements/mysql-backend-database-support-has-reached-beta) +- [MySQL 后端支持 Alpha 公告](https://releases.powersync.com/announcements/announcing-mysql-as-a-source-database-alpha) +- 本项目:`prisma/schema.prisma`、`server/index.js`、`docs/V0.2/数据库设计文档.md`、`docs/V0.2/任务模块/任务模块数据库设计文档.md` diff --git a/docs/V0.2/设计文档.md b/docs/V0.2/设计文档.md new file mode 100644 index 0000000..a32a869 --- /dev/null +++ b/docs/V0.2/设计文档.md @@ -0,0 +1,516 @@ +# iBoard V0.2 设计文档 — 打卡 / 日历 / 清单 + +| 项 | 内容 | +| --- | --- | +| 版本 | V0.2(对应分支 `dev-0.2`,应用版本 `0.2.0`) | +| 文档状态 | 已实现(与代码一致;含 2026-10-22 设计变更,见 §10) | +| 上游文档 | `需求文档.md`(需求编号 F-CK / F-CL / F-LS / F-SY / U / D / N / E 沿用本文) | +| 配套文档 | `数据库设计文档.md`、`界面设计.html` | +| 技术栈 | Tauri 2 + React 18 + antd-mobile + React Router v6 + Express 4 + Prisma/MariaDB | +| 设计基线 | 移动端优先,视口 420×820,375~430px 自适应;时区统一 `Asia/Shanghai` | + +--- + +## 1. 总体设计 + +### 1.1 目标与范围 + +在既有 iBoard 之上**新增「打卡」模块并升为默认首页**(底部 Tab 由 5 个精简为 4 个:打卡 / 宏观 / 监控 / 我),页面内以三个子视图承载三类需求: + +| 子视图 | 解决问题 | 核心隐喻 | +| --- | --- | --- | +| **打卡** | 周期性习惯的量化坚持 | 环形进度,点环即打卡 | +| **日历** | 回看昨天、执行今天、预知明天 | 月历网格 + 日状态点 | +| **清单** | 出差/回老家等一次性场景的漏项检查 | 勾选清单 + 进度条 | + +> **Tab 结构(V0.2 终态)** +> +> | 顺序 | Tab | 路由 | 说明 | +> | --- | --- | --- | --- | +> | 1 | **打卡(默认)** | `/checkin` | 根路径 `/` 重定向到此;`/checkin` 内含 打卡/日历/清单 三视图 | +> | 2 | 宏观 | `/macro/*` | 油价 / 经济数据 / 策略研究 / 经济文章 / **报警** / **新闻**(后两者由原「首页」并入) | +> | 3 | 监控 | `/monitor` | 服务器 + 米家环境 | +> | 4 | 我 | `/me` | 用户信息 + 前端 OTA 升级 | +> +> 原「首页」Tab 已移除:其中的**报警**、**新闻** 两个模块并入宏观,作为其第 5、6 个子 Tab(`/macro/alerts`、`/macro/news`),新闻详情改为 `/macro/news/:id` 以保持宏观 Tab 高亮。 + +**不纳入 V0.2**:多用户共享清单、提醒推送、数据导出、打卡项分组、年视图热力图(见 §9 演进)。 + +### 1.2 架构总览 + +``` +┌──────────────────────── 前端 (React + antd-mobile, 打包进 Tauri / OTA) ────────────────────────┐ +│ App.jsx 路由 (默认 / → /checkin) │ +│ └ /checkin ──► CheckinPage ── SwipeTabs ──┬── HabitView (打卡) │ +│ ├── CalendarView (日历) │ +│ └── ListView (清单) │ +│ └ /macro/* ──► MacroPage ── 油价/经济数据/策略研究/经济文章/报警/新闻 │ +│ 组件: RingProgress(环形) / Celebrate(特效, 恒开) / BottomNav / TabIcon │ +│ 工具: src/pages/checkin/date.js (本地日期, 与后端同口径) apiClient: src/api/checkin.js │ +└──────────────────────────────────┬─────────────────────────────────────────────────────────────┘ + │ HTTPS + JWT (Authorization: Bearer) +┌──────────────────────────────────▼─────────────────────────────────────────────────────────────┐ +│ Express (server/index.js) │ +│ /api/checkin ─► routes/checkin.js ─┐ │ +│ /api/checklists─► routes/checklists.js ─┤─► middleware.authRequired ─► Prisma Client │ +│ └─► checkin/period.js (周期口径唯一来源) │ +└──────────────────────────────────┬─────────────────────────────────────────────────────────────┘ + ▼ + MariaDB: Habit / CheckIn / CheckList / CheckListItem / CheckListTemplate +``` + +### 1.3 分层与职责 + +| 层 | 位置 | 职责 | +| --- | --- | --- | +| 页面 | `src/pages/checkin/*.jsx` | 视图编排、本地状态、乐观更新与回滚 | +| 组件 | `src/components/RingProgress.jsx` | 纯展示:SVG 环形 + 完成特效(恒开,无开关) | +| 日期工具 | `src/pages/checkin/date.js` | 与后端同口径的本地日期运算(**改一处需同步另一处**) | +| API 封装 | `src/api/checkin.js` | 统一走 `api()`(自带 token / 错误解析) | +| 路由 | `server/routes/checkin.js`、`checklists.js` | 参数校验、鉴权、组装展示态 | +| 领域逻辑 | `server/checkin/period.js` | 周期窗口、日均期望、streak 判定 | +| 持久化 | Prisma Client(`generated/prisma`) | 表操作 | + +### 1.4 目录结构(本次新增/修改) + +``` +prisma/ + schema.prisma # +5 个 model,User 加反向关系 + migrations/20261022120000_add_checkin_checklist/ # 新建表 + 外键 +server/ + checkin/period.js # 周期计算(唯一口径) + routes/checkin.js # /api/checkin/* + routes/checklists.js # /api/checklists/* + index.js # 挂载两个路由 +src/ + api/checkin.js # checkinApi / checklistApi + api/ota.js # OTA 封装 + 判新模式(version/sequence) + utils/version.js # 版本号字段比较(纯函数, 可单测) + utils/format.js # 日期格式化 + components/RingProgress.jsx # 环形进度 + Celebrate 特效 + components/BottomNav.jsx # Tab: 打卡(默认)/宏观/监控/我 + components/TabIcon.jsx # 图标集(移除 home) + pages/checkin/CheckinPage.jsx # 容器:三视图切换 + pages/checkin/HabitView.jsx # 打卡视图 + pages/checkin/CalendarView.jsx # 日历视图 + pages/checkin/ListView.jsx # 清单视图 + pages/checkin/date.js # 前端日期工具 + pages/MacroPage.jsx # 宏观: 6 个子 Tab(含报警/新闻) + pages/macro/AlertsView.jsx # 报警(原首页) + pages/macro/NewsView.jsx # 新闻(原首页) + pages/MePage.jsx # 我: OTA 组内「开发版本」开关 + App.jsx # / → /checkin; /macro/*; 移除 /news/:id + styles/globals.css # 打卡相关样式段 +scripts/ci/check-version.mjs # CI: 校验分支名 ↔ package.json 版本 +docs/V0.2/ # 需求 / 设计 / 数据库设计 / 界面设计 +``` + +--- + +## 2. 领域模型与核心口径 + +### 2.1 实体 + +`Habit`(打卡项定义)· `CheckIn`(一次打卡)· `CheckList`(场景清单)· `CheckListItem`(检查项)· `CheckListTemplate`(模板)。字段与索引见《数据库设计文档》。 + +### 2.2 周期口径(唯一来源:`server/checkin/period.js`) + +| 概念 | 定义 | +| --- | --- | +| 本地日历日 | `Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Shanghai' })` → `"YYYY-MM-DD"` | +| 周起始 | **周一**(`startOfWeek` 把周日映射到第 7 天) | +| 周期窗口 | day = [d, d];week = [周一, 周日];month = [1 号, 月末];year = [1/1, 12/31] | +| **环形进度** | `percent = 周期内累计次数 / n`,上限 1 —— 回答「这周期做了多少」 | +| **日均期望次数** | day→n;week→`ceil(n/7)`;month→`ceil(n/当月天数)`;year→`ceil(n/365)` —— 回答「今天该做多少」 | +| 完成 | `done >= target`(超额允许,`over = done - target` 继续累加) | +| streak | 从当前周期向前回溯:达标 +1;**当前周期未结束且未达标 → 跳过而非中断**;已结束周期未达标 → 终止 | + +>两种口径并存是有意为之:环形看「周期整体」,日历日标记看「当天份额」。例如「每周 3 次」,周三打 1 次 → 环形 33.3%,当天已达标(1 ≥ ceil(3/7)=1)。 + +### 2.3 边界规则 + +| 场景 | 规则 | 编号 | +| --- | --- | --- | +| 提前打卡(未来日期) | 拒绝,返回 400「不能为未来日期打卡」 | F-CK-11 | +| 补打 | 仅限最近 **7 天**,记录 `source='backfill'` | F-CK-10 | +| 跨零点 | 以服务端 Asia/Shanghai 日历日为准,前端不自行判断 | E-02 | +| 周期中途新建 | 按完整 n 计算,不按剩余天数折算 | E-01 | +| 编辑 `target` | 立即生效,历史记录不改动 | F-CK-13 | +| 删除打卡项 | 默认归档(`archived=true`),`?hard=1` 物理删并清记录 | E-05 | +| 清单删除 | 物理删(一次性场景不留历史) | F-LS-10 | + +--- + +## 3. 前端设计 + +### 3.1 路由与容器 + +``` +底部 Tab (BottomNav) 打卡(默认) ─ 宏观 ─ 监控 ─ 我 + / → +/checkin → CheckinPage + ├─ NavBar「打卡」 + └─ SwipeTabs(左右滑动切换,与既有模块交互一致) + ├─ habit → HabitView (受 date 影响) + ├─ calendar → CalendarView (持有选中 date,可回写容器) + └─ list → ListView (独立) +/macro/* → MacroPage (油价 / 经济数据 / 策略研究 / 经济文章 / 报警 / 新闻) +``` + +`activeKeyFromPath()` 对未匹配路径兜底返回**第一个 Tab(`/checkin`)**,与默认路由保持一致。 + +`date` 状态提升到 `CheckinPage`:日历里点某天 → 切回打卡视图时即为该日,实现「日历与打卡联动」。 + +### 3.2 组件树与关键状态 + +``` +CheckinPage +├── useState: active('habit'|'calendar'|'list'), date('YYYY-MM-DD') +HabitView(date) +├── data: { today, date, items[], summary:{total,done} } +├── bursts: { habitId: n } // 特效触发计数(+1 即播放一次) +└── form / showForm // 新建/编辑弹窗 +CalendarView(date, onDateChange) +├── month, cal(月历), detail(当日), bursts +└── quickValue: -1|0|1|null // 昨天/今天/明天 分段控件 +ListView() +├── archived, lists, openId(展开的清单), newItem, templates, form/showForm +└── bursts +``` + +统一约定: +- 加载中 → `DotLoading`;错误 → `error-banner` + 重试;空 → antd-mobile `Empty` + 引导按钮(三态齐全,U-06)。 +- 页面重新可见时静默刷新(`useRefreshOnVisible`),与既有模块一致。 + +### 3.3 环形进度组件 `RingProgress` + +| 项 | 设计 | +| --- | --- | +| 实现 | 双 ``:灰底环 + `stroke-dasharray/dashoffset` 进度环,`rotate(-90)` 从 12 点顺时针 | +| 尺寸 | `size=58`,`stroke=6`,`r=26`,周长 `2πr≈163.4` | +| 动画 | `transition: stroke-dashoffset .45s cubic-bezier(.4,0,.2,1)`(400ms 级,U-01) | +| 中心内容 | 未达标 → `done / target` 数字;**达标 → 对勾 SVG**(`ring-check-in` 缩放入场);超额 → 右上角 `+n` 徽标 | +| 交互 | 整个环可点 = 打卡(`clickable`),同时卡片右侧保留显式「打卡」按钮,降低发现成本 | +| 无障碍/降级 | `disabled` 态半透明;**完成特效恒开**(V0.2 设计变更:取消页面内特效开关,无任何关闭入口) | + +**完成特效 `Celebrate`**(F-CK-07 / U-02):`fire` 计数器 +1 即触发一次 → 14 个粒子沿圆周角度扩散(`--a/--d` CSS 变量)+ 主色光环脉冲 0.7s,总时长 ≤900ms,`pointer-events:none` 不阻塞后续操作;同时 `navigator.vibrate(18)` 轻震动(U-03)。**特效永远播放**,不再读 `localStorage.iboard_celebrate`,也不再响应 `prefers-reduced-motion` 关闭(用户明确要求「特效永远打开」)。 + +### 3.4 交互清单 + +| 视图 | 主要交互 | +| --- | --- | +| 打卡 | 点环/点按钮打卡(乐观更新,失败回滚 + Toast)、撤销、编辑、归档、新建弹窗(名称/周期/目标次数/颜色/备注) | +| 日历 | 月份左右切换、回到今天、「昨天/今天/明天」分段切换、点日期选中、当日详情内打卡(未来日期只读显示「计划」)、本月完成率 | +| 清单 | 进行中/已归档切换、展开/收起、逐条勾选、添加检查项、全选/全不选、复制、存为模板、归档/恢复、删除、从模板新建 | + +排序:打卡项列表**未完成在前**(F-CK-16);清单按 `updatedAt` 倒序。 + +### 3.5 视觉规范 + +| 类别 | 取值 | +| --- | --- | +| 主色 | `#1677ff`;成功 `#00b578`;警告/部分 `#ffc53d`;超额 `#a855f7`;危险 `#ff3141` | +| 背景 / 卡片 | `#f5f5f5` / `#ffffff`,卡片 `radius 10px` + `0 1px 2px rgba(0,0,0,.04)` | +| 文字 | 主 `#333`、次 `#666`、弱 `#999` | +| 日历日状态点 | full 绿 / partial 黄 / over 紫 / empty 无 | +| 打卡项预设色 | `#1677ff #00b578 #ff8f1f #ff3141 #a855f7 #14b8a6 #f43f9e` | + +完整高保真稿见 `界面设计.html`。 + +--- + +## 4. 后端设计 + +### 4.1 通用约定 + +- 两个路由文件首行 `router.use(authRequired)`,**全部接口需登录**;用户维度一律取 `req.user.sub`,不信任 body 中的 `userId`。 +- 响应统一 `{ item }` / `{ items }` / `{ ok }`;错误统一 `{ error: '中文提示' }` + 语义化状态码。 +- 展示态(percent / completed / streak / dayDone …)由服务端计算后下发,前端不做二次口径推导,避免两端不一致。 + +### 4.2 接口清单 + +#### 打卡 `/api/checkin` + +| 方法 | 路径 | 说明 | 关键响应字段 | +| --- | --- | --- | --- | +| GET | `/habits?date=` | 指定日待办 + 周期进度 | `items[]`(含 `done/target/percent/completed/over/streak/expectedPerDay/periodStart,End`)、`summary` | +| POST | `/habits` | 新建打卡项 | `item` | +| PUT | `/habits/:id` | 编辑(名称/周期/目标/颜色/备注/归档/排序) | `item` | +| DELETE | `/habits/:id?hard=1` | 默认归档;`hard=1` 物理删 | `{ ok, removed }` | +| POST | `/habits/:id/check` | 打一次卡(`{ date?, note? }`) | `item`(最新展示态) | +| DELETE | `/habits/:id/check?date=` | 撤销本周期最近一次 | `item` | +| GET | `/day?date=` | 某日每个打卡项明细 | `items[]`(含 `canCheck`)、`isToday`、`isFuture` | +| GET | `/calendar?month=YYYY-MM` | 月历 + 完成率 | `days[]`(`status/expected/actual/isToday/isFuture/habits[]`)、`rate`、`habits[]` | + +#### 清单 `/api/checklists` + +| 方法 | 路径 | 说明 | +| --- | --- | --- | +| GET | `/?archived=0|1` | 清单列表(带 `done/total/percent/completed`) | +| POST | `/` | 新建(`{ title, scene, note?, items?, templateId? }`) | +| GET | `/:id` | 单条详情 | +| PUT | `/:id` | 改名称/场景/备注/归档 | +| DELETE | `/:id` | 物理删除(连带检查项) | +| POST | `/:id/duplicate` | 复制为新清单(勾选状态重置) | +| POST | `/:id/items` | 新增检查项 | +| PUT | `/items/:id` | 改标题/备注/勾选(`done` 变更时写 `doneAt`) | +| DELETE | `/items/:id` | 删除检查项 | +| POST | `/:id/check-all` | 一键全选/全不选(`{ done }`) | +| GET | `/templates` | 模板列表(含系统预置,首次访问幂等写入) | +| POST | `/templates` | 另存为模板 | +| DELETE | `/templates/:id` | 删除**自建**模板(预置模板拒绝) | + +### 4.3 关键流程:打卡 + +``` +用户点环 + │ + ├─ 前端: 本地 +1,重算 percent/completed/over(乐观更新) + │ + └─ POST /api/checkin/habits/:id/check + ├─ 校验 habit 归属 → 404 + ├─ date > today → 400 不能为未来日期打卡 + ├─ date < today-7 → 400 最多只能补打最近 7 天 + ├─ seq = max(seq)+1 → insert + │ └─ P2002 → 409 该次打卡已记录(幂等) + └─ 返回最新 item + │ + ├─ 成功: 用服务端 item 覆盖本地;若 completed → fire 特效 + └─ 失败: setData(before) 回滚 + Toast +``` + +### 4.4 关键流程:月历聚合 + +1. 取全部 Habit(含归档)→ 过滤 `archived=false` 得 `active`。 +2. 一次查询当月 `[01, 月末]` 全部 CheckIn,按 `habitId → date → count` 分组。 +3. 逐日遍历:仅统计 `Habit.createdAt <= 该日` 的项;累加 `expected += expectedPerDay(...)`、`actual += min(count, expected)`;任一 `count > expected` 记 `over`。 +4. `status`:全完成且无超额 → `full`;全完成且有超额 → `over`;有完成未全完成 → `partial`;否则 `empty`。 +5. 完成率 `rate = Σactual / Σexpected`(**只统计非未来日期**)。 + +复杂度:`O(天数 × 打卡项数)`,30×20=600 次内存运算,无 N+1 查询。 + +### 4.5 校验与错误码 + +| 状态码 | 场景 | +| --- | --- | +| 400 | 名称为空/超长、周期非法、target < 1、未来日期打卡、超出补打窗口、本周期无记录可撤销、模板缺条目 | +| 404 | 资源不存在或不属于当前用户 | +| 409 | 并发重复打卡(P2002) | +| 401 | 未登录 / token 失效(`authRequired`) | + +所有写操作均用 `prisma.xxx.findFirst({ where: { id, userId } })` 或 `where: { id, list: { userId } }` 做归属校验,杜绝越权改他人数据。 + +--- + +## 5. 非功能设计 + +| 维度 | 设计 | +| --- | --- | +| 首屏 | 三视图懒切换,只有激活视图发请求;单视图首屏 ≤ 1 次列表请求(N-01) | +| 弱网/失败 | 乐观更新 + 失败回滚 + Toast;错误横幅带重试;`useRefreshOnVisible` 静默刷新 | +| 幂等 | 唯一约束 `(habitId, date, seq)` + 前端按钮 loading,防连点重复计次 | +| 时区 | 全链路 Asia/Shanghai;库内 `DATETIME` 存 UTC,业务日期用 `VARCHAR(10)` 本地日历日 | +| 兼容 | 375~430px 自适应;Tauri Android WebView 支持 `navigator.vibrate`,不支持时静默跳过 | +| 性能 | 月历/周期统计均为「一次范围查询 + 内存聚合」,避免逐项查询 | +| 安全 | 全接口 `authRequired`;输入长度截断(名称 20、检查项 40、备注 200);无 SQL 拼接(Prisma 参数化) | +| 可维护 | 周期口径单点(`server/checkin/period.js`),前端 `date.js` 与之对齐,改动需同步两处(已在文档与注释中标注) | + +--- + +## 6. 测试与验证 + +| 层级 | 内容 | 结果 | +| --- | --- | --- | +| 单元 | 周期工具 23 条断言(窗口边界、周一起始、月末、闰年、日均期望、streak 跳过未结束周期) | 全通过 | +| 单元 | 版本比较 10 条断言(`-ota` 后缀、`v` 前缀、缺位补 0、`0.10 > 0.9`、`beta.3 < beta.10`、空串) | 全通过 | +| 接口冒烟 | 自签 JWT 端到端:每天 1 次闭合、每周 3 次 33.3%、超额 +1、撤销归零、未来日期 400、月历 31 天、模板生成 11 项、全选完成、复制重置 | 全通过,测试数据已清理 | +| 构建 | `npm run build` | 通过 | +| 迁移 | `prisma migrate deploy` 到目标库 | 成功 | + +--- + +## 7. 里程碑(对应需求文档 M1~M5) + +| 里程碑 | 内容 | 状态 | +| --- | --- | --- | +| M1 | 数据模型 + 迁移 + 周期模块 + 全部后端接口 | 完成 | +| M2 | 打卡视图:环形进度、特效、撤销/补打、新建编辑 | 完成 | +| M3 | 日历视图:月历 + 昨天/今天/明天 + 完成率 | 完成 | +| M4 | 清单视图:CRUD、勾选、进度、模板复用 | 完成 | +| M5 | 文档(需求/设计/数据库/界面)+ 构建校验 + 提交 | 完成 | + +--- + +## 8. 风险与权衡 + +| 项 | 权衡 | 缓解 | +| --- | --- | --- | +| 周期口径前后端各实现一份 | 前端需离线即时反馈,不能等接口 | 两端算法同源同注释;口径变更必须同步 `period.js` 与 `date.js`(已写入项目记忆) | +| 进度实时聚合而非冗余计数 | 换取一致性与配置即时生效,牺牲少量 CPU | 单用户数据量极小(月历 30 天 × 20 项),实测无感;V0.3 可加汇总表 | +| 未来日期禁止打卡 | 防止「提前打完一周」刷进度,符合「日历看明天=计划」语义 | 明天视图只读展示「计划」,明确文案 | +| 特效恒开可能打扰 | 用户明确要求「特效永远打开」,不再提供开关 | 单次特效 ≤900ms 且 `pointer-events:none`,不影响连续打卡;若后续需要再评估恢复开关 | +| Tab 调整为 4 个(打卡置左并默认) | 首页被取消,报警/新闻并入宏观后宏观有 6 个子 Tab | 子 Tab 可横向滚动;报警角标保留在子 Tab 上;底部 4 项在 375px 下热区充足(≥44px) | + +--- + +## 9. 后续演进(V0.3+) + +1. **提醒与通知**:`Habit.remindAt` + Tauri 本地通知,解决「忘打卡」。 +2. **补打放宽 / 历史编辑**:当前 7 天限制可配置化。 +3. **统计视图**:连续天数榜单、月/年热力图(届时引入 `HabitDailyStat` 汇总表)。 +4. **打卡项分组与排序拖拽**:现有 `sortOrder` 已预留,仅需交互。 +5. **清单协作/分享**:引入 `CheckListShare`。 +6. **数据导出**:清单完成后导出为图片或文本(便于发群里确认)。 + +--- + +## 10. 设计变更记录(2026-10-22) + +### 10.1 变更清单 + +| # | 类型 | 变更 | 影响文件 | +| --- | --- | --- | --- | +| 1 | Bug | OTA 包版本与分支不对应:`dev-0.2` 上仍是 `0.1.0` → OTA 报成 `0.1.0-ota`。统一升到 `0.2.0`,OTA 包变为 **`0.2.0-ota`**,并加 CI 强校验 | `package.json`、`src-tauri/tauri.conf.json`、`src-tauri/Cargo.toml`、`Cargo.lock`、`scripts/ci/check-version.mjs`、`docker-build.yml` | +| 2 | 功能 | 我的页 OTA 组新增**「开发版本」Switch**(默认关),切换判新口径:开=`build sequence`,关=版本号字段比较;开关值持久化在设备端 | `src/api/ota.js`、`src/utils/version.js`、`src/pages/MePage.jsx` | +| 3 | 设计 | 打卡 Tab 移到**最左边并默认选中**;打卡页移除**特效开关**,特效永远打开 | `BottomNav.jsx`、`App.jsx`、`TabIcon.jsx`、`HabitView.jsx`、`RingProgress.jsx`、`globals.css` | +| 4 | 设计 | 取消「首页」Tab,其**报警 / 新闻**并入宏观,成为宏观的第 5、6 个子 Tab | `MacroPage.jsx`、新增 `macro/AlertsView.jsx`、`macro/NewsView.jsx`、删除 `HomePage.jsx` | + +### 10.2 版本号为什么必须跟分支走 + +`scripts/build-ota.mjs` 里 `manifest.version = package.json.version + '-ota'`,也就是**版本号的唯一来源仍是 `package.json`**。 +分支按版本命名(`dev-0.2`),但版本号是手工改的,一旦忘改就会出现「分支 0.2、包版本 0.1」的错位,客户端判新逻辑全部失准。 + +**同一版本号的两种产物写法**(2026-10-22 补充确认): + +``` +分支 dev-0.2 → 0.2.0 + ├─ APK / 桌面安装包 0.2.0 裸版本号,无后缀 + ├─ 前端运行时 0.2.0 裸版本号,无后缀 + └─ OTA 热更包 0.2.0-ota 多一个 -ota 标记 +``` + +即 APK 与 OTA 的版本主体完全一致,唯一差别是 OTA 多 `-ota` 后缀;`package.json` 只写裸版本号,后缀由打包脚本追加。 + +| 环节 | 取值 | 现状 | +| --- | --- | --- | +| `package.json` `version` | `0.2.0` | 唯一手动管理点,**不得带 `-ota`** | +| `src-tauri/tauri.conf.json` `version` | `0.2.0` | 决定 **APK `versionName`**(经 `tauri android init` 写入 `tauri.properties`)与桌面包版本 | +| `src-tauri/Cargo.toml` / `Cargo.lock` | `0.2.0` | Rust 包版本,与上两者保持一致 | +| OTA `latest.json.version` | `0.2.0-ota` | 打包脚本自动拼 `-ota` 后缀 | +| `src/version.js` `APP_VERSION` | `0.2.0` | 前端展示与版本比较的基准 | +| APK `versionCode` | `2000` | `major*1000000 + minor*1000 + patch`,必须单调递增才能覆盖安装(`0.1.0` = 1000) | +| OTA `latest.json.min_binary_version` | `0.1.0` | **与前端版本解耦**:前端 bundle 要求的最低原生二进制版本。现有 APK 原生层仍是 0.1.0,跟着抬会导致旧 APK 收不到更新,CI 固定传 `--min-binary 0.1.0` | +| CI 守卫 | `scripts/ci/check-version.mjs` | ① 四处版本必须完全相等且不含 `-ota`(`main`/`master` 也查);② 从 `GITHUB_REF_NAME` 解析 `dev-.`,要求 `version` 以该前缀开头;任一不满足**构建直接失败**。`docker-build.yml` 与 `android-build.yml` 都在构建前执行 | + +### 10.3 判新口径:版本号 vs build sequence + +「我的 → 前端升级 (OTA)」组内新增 `开发版本` 开关(antd-mobile `Switch`),默认**关闭**: + +| 开关 | 判新依据 | 实现路径 | 适用场景 | +| --- | --- | --- | --- | +| **关(默认)** | **版本号字段大小** | `GET /api/ota/check/0` 取 `manifest.version` → `compareVersion(manifest.version, 当前运行版本) > 0` 才算新 | 生产口径。旧分支被重新构建(sequence 变大但版本更小)时不会把设备拉回旧版 | +| **开** | **build sequence** | `tauri-plugin-hotswap` 的 `checkUpdate()`(内部拿本地 sequence 请求 `/api/ota/check/:seq`,服务端只回比它大的) | 开发联调。每次构建 sequence 都变,保证任何一次提交都能推到设备上 | + +补充约定: + +- **持久化**:`localStorage['iboard_ota_dev_mode'] = '1' | '0'`(默认 `'0'`)。值只存在设备端 WebView 存储里,不进数据库、不上报,换设备/清数据即回默认。 +- **当前运行版本**:已应用过 OTA 覆盖包时取覆盖包 `version`(插件 `getVersionInfo()`),否则取内置包 `APP_VERSION`;避免「内置包 0.1.0 + 覆盖包 0.2.0」时永远判定有新版本。 +- **切换开关会重置检查结果**(`otaState='idle'`、清空 `otaInfo`),避免上一种口径的结论误导用户。 +- **Web 端**:浏览器里拿不到本地 sequence。默认口径按版本号比较;开发口径退化为「服务端版本 ≠ 本地版本即算新」(近似 sequence 语义)。Web 端只提示刷新,不下载不替换,与原设计一致。 + +**版本比较算法**(`src/utils/version.js`,纯函数可单测): + +```js +parseVersionFields('0.2.0-ota') // → [0, 2, 0] +compareVersion('0.2.0-ota','0.1.0') // → 1 (0.2.0 更新) +compareVersion('0.2','0.2.0') // → 0 (缺位补 0) +``` + +规则:按非数字字符切段 → 过滤空段 → 转数字 → 逐段比较,位数不足补 0。单测 10 条覆盖 `-ota` 后缀、`v` 前缀、多位段(`0.10 > 0.9`)、空串、`beta.3 < beta.10`,全部通过。 + +### 10.4 交互与信息架构变更 + +| 项 | 变更前 | 变更后 | +| --- | --- | --- | +| 底部 Tab | 首页 / 宏观 / 监控 / 打卡 / 我(5 个) | **打卡 / 宏观 / 监控 / 我(4 个)**,打卡在最左 | +| 默认落地 | `/` → 首页(默认选中新闻) | `/` → **`/checkin`**;`RedirectIfAuthed` 与 404 兜底同样指向 `/checkin` | +| 报警 / 新闻 | 首页内的 2 个 Tab | 宏观的第 5、6 个子 Tab(`/macro/alerts`、`/macro/news`),报警角标保留 | +| 新闻详情 | `/news/:id`(底部无 Tab 高亮) | **`/macro/news/:id`**(宏观 Tab 保持高亮) | +| 打卡页头部 | `[特效开/关]` + `[+ 新建]` | 只保留 `[+ 新建]`;特效恒开 | +| 特效实现 | `effectsEnabled()` 读 `localStorage.iboard_celebrate` + `prefers-reduced-motion` 降级 | 删除开关与降级判断,`Celebrate` 只要 `fire` 变化就播放 | + +### 10.5 界面设计同步 + +`界面设计.html` 已同步:底部 Tab 改为 4 项且「打卡」高亮在首位、打卡页去掉特效开关按钮、新增「我的 · OTA 开发版本开关」屏、宏观新增「报警 / 新闻」子 Tab 与合并后的 6 Tab 导航。 + +--- + +## 11. 界面调整(2026-10-23) + +### 11.1 变更清单 + +| # | 范围 | 变更 | 状态 | +| --- | --- | --- | --- | +| 1 | 页面头部 | 打卡 NavBar 高度减半(45 → 23px)、标题改主色 12px;宏观 / 监控补标题;「我」页整条去掉 | **已实现** | +| 2 | 打卡页 | 日历与打卡合并为一个视图(上日历、下打卡,按天/周/月分组、去掉年分组、卡片紧凑化) | **待确认**(设计稿已出,见《界面设计.html》2.12 ②节) | +| 3 | 清单页 | 新增「模板」分类(与进行中 / 已归档并列),独立 CRUD + 字母序;新建清单去掉场景、模板改右侧滑出选择 | **已实现** | +| 4 | 宏观页 | 报警 / 新闻移到最左并默认选中新闻;tab 条增加右侧溢出提示 | **已实现** | + +### 11.2 页面头部统一 + +新增组件 `src/components/PageHeader.jsx`(高度 23px、标题 12px/600 主色、字距 1px、背景取页面底色 `#f5f5f5`),只被 **打卡 / 宏观 / 监控** 三页引用: + +| 页 | 变更前 | 变更后 | +| --- | --- | --- | +| 打卡 | `NavBar` 45px,标题 17px 深色 | `PageHeader` 23px,标题主色 | +| 宏观 | `NavBar` 45px,空白 | `PageHeader` + 标题「宏观」 | +| 监控 | `NavBar` 45px,空白 | `PageHeader` + 标题「监控」 | +| 我 | `NavBar` 45px 空白条 | **移除**,`me-profile` 直接贴顶 | + +> 「我」页不引用该组件,因此调整打卡/宏观/监控的头部不会波及「我」;反之亦然。三个页面之间也各自持有独立实例,互不影响。 + +### 11.3 清单模板分类 + +**接口**(`server/routes/checklists.js`): + +| 方法 | 路径 | 说明 | +| --- | --- | --- | +| GET | `/api/checklists/templates` | 列表,**服务端按名称字母顺序排序**(原为 `builtin desc, id asc`) | +| GET | `/api/checklists/templates/:id` | 详情(新增) | +| POST | `/api/checklists/templates` | 新建(名称 ≤20 字,≥1 条检查项) | +| PUT | `/api/checklists/templates/:id` | 改名称 / 检查项(新增;`builtin=true` 拒绝,404) | +| DELETE | `/api/checklists/templates/:id` | 删自建模板(`builtin=true` 拒绝) | + +排序口径:`Intl.Collator('zh-Hans-CN', { numeric: true, sensitivity: 'base' })` —— 中文按拼音、数字按数值。前端 `ListView` 拿到列表后再排一次,保证与后端一致(网络返回顺序不可控时也不乱)。 + +**前端**(`src/pages/checkin/ListView.jsx`): + +- 分段由 2 项扩为 3 项:`active`(进行中)/ `archived`(已归档)/ `template`(模板);右上角按钮随分段切换为「+ 新建」/「+ 新建模板」。 +- 模板卡片:名称 + 「预置」标记 + 项数;最多展示 4 条检查项摘要,超出折叠为 `+n`;操作「由此新建 / 编辑 / 删除」,预置模板只保留「由此新建」。 +- 新建 / 编辑模板共用底部弹层:名称 + 检查项逐条编辑(≤40 字,可删可加)。 +- **新建清单表单**:仅「名称」+「模板」两项 —— **场景选择器已移除**(`scene` 改为沿用所选模板自带的场景,无模板则为空;数据库字段保留,历史数据照常展示)。 +- **模板选择**:点击模板行从**右侧滑出** `78vw` 面板(antd-mobile `Popup position="right"`),首项固定「不使用模板」,其后为字母序模板列表(预置带标记 + 项数),选中项主色高亮。 + +### 11.4 宏观 tab 顺序与溢出提示 + +- Tab 顺序改为 **报警 → 新闻 → 油价 → 经济数据 → 策略研究 → 经济文章**,默认选中 **新闻**;`/macro` 由路由直接 `Navigate` 到 `/macro/news`(`App.jsx`),`activeFromPath` 兜底同样返回 `news`。 +- `SwipeTabs` 增加溢出提示层:测量 `.adm-tabs-tab-list` 的 `scrollLeft + clientWidth < scrollWidth`,为真时在顶部右侧渲染 `.tab-more-hint`(白色渐变 + 主色圆底箭头「›」,1.5s 轻微摆动动画,点击向右翻页 60% 视宽),滚到末尾自动消失。 +- 切换 tab 时把激活项平滑滚到可视区中央,避免选中项藏在屏幕外。 +- 为什么不用框架自带的:antd-mobile Tabs 自带 `.adm-tabs-header-mask-right` 只有 30px 纯渐变、无箭头、无交互,用户难以判断"右侧还有内容",故叠加此提示层。 + +### 11.5 待确认项:日历与打卡合并 + +方案已写入《界面设计.html》2.12 ②节(含主视图、未来日期计划态、卡片「⋯」操作面板三屏)。要点: + +| 项 | 方案 | +| --- | --- | +| 结构 | SwipeTabs 由 3 个减为 2 个:`日历(含打卡)` / `清单` | +| 日历 | 月历 + 月份切换 + 回到今天;**去掉「昨天/今天/明天」分段**;日期格 38 → 31px | +| 打卡 | 沿用现有卡片样式,紧凑化:内边距 12→8px、环形 58→42px(环宽 6→5)、间距 10→7px | +| 操作 | 撤销 / 编辑 / 归档收进「⋯」底部面板(原为卡片底部三个文字链接) | +| 分组 | **每天 → 每周 → 每月**,组标题左侧主色竖条;**去掉「每年」分组** | +| 联动 | 点日期即切换当日数据;未来日期只读,按钮灰显「计划」 | + +确认后再落地:`CheckinPage` 收敛为 2 个 tab、`CalendarView` 与 `HabitView` 合成新视图组件、`HabitView` 的循环/编辑逻辑抽出复用、样式新增 `.mv-*` 段。 + + diff --git a/docs/V0.2/需求文档.md b/docs/V0.2/需求文档.md new file mode 100644 index 0000000..b8585a9 --- /dev/null +++ b/docs/V0.2/需求文档.md @@ -0,0 +1,348 @@ +# iBoard V0.2 需求文档 — 打卡 / 日历 / 清单 + +> 版本:V0.2 · 分支:`dev-0.2` · 日期:2026-10-22 +> 目标项目:iboard(Tauri 2 + React 18 + antd-mobile + Express + Prisma/MariaDB,移动端优先) +> 基线版本:V0.1(`dev-0.1`) + +--- + +## 1. 总体描述 + +在 iBoard 移动端新增一个「打卡」模块一个「清单」模块,满足两类日常需求: +### 打卡 +1. **周期性打卡**(习惯养成):用户定义"每天 n 次 / 每周 n 次 / 每月 n 次 / 每年 n 次"的目标, + 每次打卡后卡片上的**环形进度**按 `已完成次数 / 目标次数` 增长,达标时环形闭合并播放完成特效; +2. **日历回看**:以日历为载体,快速查看**今天**(要做什么)、**昨天**(做得怎样)、 + **明天**(计划做什么),日期上以圆点/实心标记完成情况; + +### 清单 +1. **清单**(场景化一次性事项):面向**出差、回老家**等临时场景,逐条勾选确认, + 避免遗漏;清单可复用为模板,下次一键生成。 + +### 1.1 本期范围 + +- 单用户使用,数据按登录用户隔离(复用现有 JWT 鉴权)。 +- 移动端优先(`MobileShell` + 底部 TabBar 体系),桌面浏览器兼容可用即可。 +- 数据落库 MariaDB,通过现有 Express `/api/*` 提供接口。 + +### 1.2 不在本期范围(明确排除) + +- 多人协作 / 共享打卡 / 打卡排行榜。 +- 系统通知、推送提醒、定时提醒(V0.2 不引入通知能力)。 +- 复杂统计报表(周报/月报/趋势图),本期只提供完成率概览。 +- 桌面端专用布局、横屏适配。 +- 清单的周期性重复(如"每次出差都生成"),本期只做**临时清单 + 模板手动复用**。 + +--- + +## 2. 术语与概念定义 + +| 术语 | 定义 | +| --- | --- | +| **打卡项(Habit)** | 一个被跟踪的周期性目标,如"喝水 每天 8 次""健身 每周 3 次" | +| **周期类型** | `day` / `week` / `month` / `year`,决定进度统计窗口 | +| **目标次数 n** | 一个周期内需要完成的打卡次数,n ≥ 1 | +| **周期窗口** | 日=自然日;周=自然周(周一 00:00 至周日 23:59);月=自然月;年=自然年。时区统一 Asia/Shanghai | +| **打卡记录(CheckIn)** | 一次打卡动作,归属到某个**本地日历日**(可补打到过去日期) | +| **环形进度** | 卡片上的圆环组件,`进度 = min(已完成次数 / n, 1)`;达标=闭合 | +| **清单(CheckList)** | 一次性检查清单,如"出差 · 10月杭州",含若干检查项 | +| **检查项(CheckItem)** | 清单中的单条待检查事项,勾选即完成 | +| **模板(Template)** | 可被复用的清单结构(一组检查项标题),用于一键生成新清单 | +| **日期状态** | 某日的打卡完成情况:全部完成 / 部分完成 / 未开始 / 超额完成 | + +--- + +## 3. 需求点 + +**编号规则**:`类型-模块-序号`。类型:F=功能需求、U=交互与体验、D=数据、N=非功能、E=异常与边界。 +模块:CK=打卡、CL=日历、LS=清单、SY=通用。 + +**优先级**:P0 = 本期必须有;P1 = 本期应该有;P2 = 有则更好(可延后)。 + +### 3.1 功能需求 — 打卡(F-CK) + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| F-CK-01 | 支持新建打卡项:名称、周期类型、目标次数 n、颜色、图标、备注 | 表单校验:名称必填且 ≤ 20 字;n 为 ≥1 的整数 | P0 | +| F-CK-02 | 周期类型支持**每天 n 次 / 每周 n 次 / 每月 n 次 / 每年 n 次**四种 | 四种类型均可创建并正确统计 | P0 | +| F-CK-03 | 卡片以**环形进度**展示当前周期完成情况,环中心显示 `已完成/n` | 进度 = 已完成次数 / n,上限 100% | P0 | +| F-CK-04 | **每天 1 次**的打卡项:打卡后环形**直接闭合**(100%),卡片进入"已完成"态 | 一次点击即闭合,不出现 50% 之类的中间态 | P0 | +| F-CK-05 | **每周 n 次**(及月/年):每打卡一次环形闭合对应百分比(如 n=3,打 1 次=33.3%、2 次=66.7%、3 次=100% 闭合) | 进度随每次打卡实时刷新,带过渡动画 | P0 | +| F-CK-06 | 打卡主操作:点击卡片上的打卡按钮即完成一次打卡 | 单击即生效,响应 < 300ms(乐观更新) | P0 | +| F-CK-07 | 打卡**完成特效**:达标(进度达 100%)时播放特效——环形闭合动画 + 光晕/粒子扩散 + 轻量触感反馈 | 特效时长 ≤ 800ms,不阻塞后续操作 | P0 | +| F-CK-08 | 打卡记录需记录**发生日期**(本地日历日)与时间戳 | 支持按日回看,跨零点归属正确 | P0 | +| F-CK-09 | 支持**撤销**:可撤销本周期内最近一次打卡(主要是误触场景) | 撤销后进度回退,记录物理删除或标记撤销 | P1 | +| F-CK-10 | 支持**补打卡**:对过去日期补记一次打卡,默认允许回溯 7 天 | 补打后对应日期的状态与进度同步更新 | P1 | +| F-CK-11 | **不允许提前打卡**(未来日期不可打卡) | 明天的项只展示为"计划",按钮置灰 | P0 | +| F-CK-12 | 展示**连续完成天数/周期数(streak)** | 如"已连续 5 天",断签归零 | P1 | +| F-CK-13 | 打卡项可**编辑**(改名/改 n/改颜色/改周期)与**停用(归档)** | 归档后不出现在今日待办,历史保留 | P1 | +| F-CK-14 | 修改目标次数 n 后,**当前周期**进度按新 n 重算展示,不改写历史记录 | 例如 n 由 3 改 5,已完成 3 次 → 显示 3/5(60%) | P1 | +| F-CK-15 | **超额打卡**:周期内超过 n 仍可继续打卡,环形保持闭合,中心显示 `n+k`(如 3+1) | 不清零、不报错 | P2 | +| F-CK-16 | 打卡项支持**排序**(拖动或置顶)与分组展示 | 今日列表按"未完成在前、已完成在后"默认排序 | P2 | +| F-CK-17 | 打卡时可附带**备注**(可选,纯文本) | 列表详情中可查看 | P2 | + +### 3.2 功能需求 — 日历(F-CL) + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| F-CL-01 | 提供**月历视图**:当月日历网格,可切换上/下月,点击日期查看当日详情 | 周一起始,周一~周日七列 | P0 | +| F-CL-02 | **今天**视图(默认):列出今天应打卡的项及完成状态,可直接打卡 | 打开页面默认定位今天 | P0 | +| F-CL-03 | **昨天**视图:展示昨天各打卡项的**已完成结果**(完成/未完成/超额),未完成项可进行补打 | 以回看为主,标记为"已完成的历史" | P0 | +| F-CL-04 | **明天**视图:展示**明天计划要打卡的项**(按周期规则推算),只读,不可打卡 | 明确标注"计划",打卡按钮不可用 | P0 | +| F-CL-05 | 今天/昨天/明天**快捷切换**(顶部 Segmented 或左右滑动) | 一键切换,切换有过渡动画 | P0 | +| F-CL-06 | **日期标记**:月历中每个日期按完成情况标记——全部完成(实心)、部分完成(半填充)、未开始(空心/无标记)、超额完成(特殊标记) | 一眼能看出当月坚持情况 | P0 | +| F-CL-07 | **日期详情**:点击某日期,展示当天每个打卡项的"目标次数 / 已完成次数 / 状态" | 过去日期可补打,未来日期只读 | P0 | +| F-CL-08 | 日历与打卡**数据同源**:打卡操作在日历视图与打卡列表视图中即时一致 | 任一处打卡,另一处刷新 | P0 | +| F-CL-09 | 月历顶部展示**当月完成率**(完成次数 / 应完成次数) | 百分比展示 | P1 | +| F-CL-10 | 支持左右**滑动手势**切换月份 | 与按钮切换等价 | P2 | +| F-CL-11 | 月视图支持简单的**完成热力**(按完成率深浅着色) | 可选展示,默认关闭 | P2 | + +### 3.3 功能需求 — 清单(F-LS) + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| F-LS-01 | 支持新建**检查清单**:名称、场景标签(出差 / 回老家 / 自定义)、备注 | 名称必填,场景标签可空 | P0 | +| F-LS-02 | 清单内可**增删改检查项**,支持排序 | 单条 ≤ 40 字 | P0 | +| F-LS-03 | **勾选完成**:点击检查项前的复选框即打勾,已完成项置灰 + 删除线;可取消勾选 | 单击切换,状态即时保存 | P0 | +| F-LS-04 | 清单头部显示**进度**:`已完成 / 总数` + 进度条;全部完成时显示完成态 | 全部勾完触发完成提示 | P0 | +| F-LS-05 | 清单**全部完成**时播放完成特效(与 F-CK-07 同一套特效组件) | 复用,风格统一 | P1 | +| F-LS-06 | **模板**:系统预置常用模板(出差、回老家),一键基于模板生成新清单 | 至少 2 个预置模板 | P1 | +| F-LS-07 | **另存为模板**:用户可将自己的清单保存为自定义模板,后续复用 | 自定义模板可删除 | P1 | +| F-LS-08 | **复用历史清单**:从历史清单复制生成一份新清单(勾选状态重置) | 原清单保持不变 | P1 | +| F-LS-09 | 清单**归档/删除**:完成的清单可归档(不出现在进行中列表),可查看历史 | 删除需二次确认 | P1 | +| F-LS-10 | 清单项支持**备注/数量**说明(如"充电器 ×1") | 展示在标题下方,弱色小字 | P2 | +| F-LS-11 | 清单支持**一键全部勾选 / 全部取消** | 批量操作入口 | P2 | + +### 3.4 功能需求 — 通用(F-SY) + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| F-SY-01 | 底部 TabBar 新增**「打卡」页**,并作为**默认首页放在最左**;「清单」不单独占 Tab,作为打卡页内的第三个子视图 | 图标风格与现有 TabIcon 一致;启动即进入 `/checkin` | P0 | +| F-SY-02 | 「打卡」页内集成 打卡/日历 两个功能,日历放在上半截,默认显示一行为周视图,下拉显示为月视图。 | 日历中选择时间与下部打卡任务联动 | P0 | +| F-SY-03 | 所有接口复用现有鉴权(`authRequired`,JWT),数据按 `userId` 隔离 | 未登录跳转 `/login` | P0 | +| F-SY-04 | 后端新增 REST 路由(建议 `/api/checkin/*`、`/api/checklist/*`),挂载到 `server/index.js` | 与现有路由风格一致 | P0 | +| F-SY-05 | 时间处理统一 **Asia/Shanghai**,前端与后端对"日历日"的判定一致 | 跨零点打卡归属当天 | P0 | +| F-SY-06 | 打卡/勾选操作采用**乐观更新**,接口失败时回滚并 Toast 提示 | 失败可见、状态不失真 | P1 | +| F-SY-07 | 写接口**幂等/防抖**:同一打卡项 1 秒内重复点击只记一次 | 前端防抖 + 后端唯一约束双保险 | P1 | +| F-SY-08 | 页面可见性变化时刷新数据(复用 `useRefreshOnVisible`) | 从后台切回自动同步 | P1 | + +### 3.5 交互与体验需求(U) + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| U-01 | **环形进度组件**:SVG 圆环,背景环 + 进度环,进度变化带 400ms ease-out 动画 | 组件可复用(打卡卡片、清单进度共用) | P0 | +| U-02 | **完成特效**:达标瞬间环形闭合 + 光晕/粒子扩散,约 600ms;可重复观看不烦扰 | 不阻塞操作,动画结束自动清理 | P0 | +| U-03 | **触感反馈**:完成打卡时轻量震动(Web Vibration API / Tauri) | 固定开启,与特效一同触发 | P1 | +| U-04 | 移动端适配:375~430px 视口,复用 `globals.css` 的 `.card` 体系,安全区与底部 TabBar 不遮挡内容 | 真机无横向滚动、无遮挡 | P0 | +| U-05 | 空态 / 加载态 / 错误态三态齐全(首次使用引导"创建第一个打卡项") | 空态有明确 CTA | P0 | +| U-06 | 打卡按钮足够大(点击热区 ≥ 44×44),符合移动端触控规范 | 误触率低 | P1 | +| U-07 | 动效**性能降级**:特效保持轻量(单次 ≤900ms、`pointer-events:none`),不给低端机造成卡顿 | 不卡顿、不阻塞操作 | P1 | +| U-08 | 深浅色:跟随现有浅色主题,卡片/分割线风格与首页、监控页一致 | 视觉统一 | P1 | +| U-09 | 清单勾选有明确的勾选动画(勾形描边动画) | 反馈即时 | P2 | + +### 3.6 数据需求(D) + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| D-01 | 新增 Prisma 模型:`Habit`(打卡项)、`CheckIn`(打卡记录) | 字段见附录 A | P0 | +| D-02 | 新增 Prisma 模型:`CheckList`(清单)、`CheckListItem`(检查项)、`CheckListTemplate`(模板,P1) | 字段见附录 A | P0 | +| D-03 | `Habit` / `CheckList` 均带 `userId` 外键,删除用户级联删除 | 数据隔离 | P0 | +| D-04 | `CheckIn` 建立 `(habitId, date)` 索引,支持按日期范围查询 | 日历月查询走索引 | P0 | +| D-05 | 提供迁移 SQL 并纳入 `prisma migrate deploy` 流程 | 与 V0.1 迁移流程一致 | P0 | +| D-06 | 软删除语义:`Habit.archived`、`CheckList.archived`,历史记录保留 | 不物理删除历史 | P1 | + +### 3.7 非功能需求(N) + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| N-01 | 页面首屏数据加载 < 1.5s(内网/正常网络),交互响应 < 300ms(乐观更新) | 真机可感知流畅 | P0 | +| N-02 | 兼容 Android WebView(Tauri 打包)与移动端浏览器,不依赖桌面专有 API | 真机验证 | P0 | +| N-03 | 单用户数据量按"千级记录/年"设计,无需分表;月查询一次请求返回 | 日历切月无等待感 | P1 | +| N-04 | 所有接口 `authRequired`,越权访问返回 401 | 安全基线 | P0 | +| N-05 | 前端代码组织遵循现有约定:`src/pages/checkin/*` + `src/components/*` + `server/routes/*` | 结构一致 | P0 | + +### 3.8 异常与边界需求(E) + +| 编号 | 需求描述 | 处理约定 | 优先级 | +| --- | --- | --- | --- | +| E-01 | 重复点击打卡 | 前端防抖 + 后端唯一约束,只记一次 | P0 | +| E-02 | 接口失败 / 断网 | 乐观更新回滚 + Toast 提示,可重试 | P0 | +| E-03 | **周期中途新建**打卡项(如周三创建"每周 3 次") | 本期按**完整目标 n** 计算,不按剩余天数折算 | P1 | +| E-04 | 每月 n 次在天数不同的月份 | 按**自然月**窗口,不折算 | P0 | +| E-05 | 每年 n 次跨年 | 按**自然年**窗口,年末清零 | P0 | +| E-06 | 删除打卡项 | 归档(软删除)为主;硬删除需二次确认并提示"历史记录将一并删除" | P1 | +| E-07 | 清单项全部删除后清单进度为 0/0 | 显示空清单,不显示 100% | P1 | +| E-08 | 跨零点打卡(23:59 → 00:00) | 以**提交时刻的本地日历日**归属 | P0 | + +### 3.9 功能需求 — 版本与升级(F-OT,2026-10-22 追加) + +| 编号 | 需求描述 | 验收要点 | 优先级 | +| --- | --- | --- | --- | +| F-OT-01 | **OTA 包版本必须与分支对应**:`dev-0.2` 分支上 OTA 包版本应为 `0.2.0-ota` | `GET /api/ota/check/0` 返回 `version = 0.2.0-ota` | P0 | +| F-OT-01a | **APK 版本与 OTA 版本同源**:APK(含桌面包)版本为裸版本号 `0.2.0`,无后缀;OTA 版本为同一版本号加 `-ota`。两者都跟代码分支保持一致 | APK 系统信息里 versionName=`0.2.0`(versionCode=2000),OTA manifest=`0.2.0-ota` | P0 | +| F-OT-02 | 版本号错位在 CI 阶段就拦住:①`package.json`/`tauri.conf.json`/`Cargo.toml`/`Cargo.lock` 四处必须完全一致且不含 `-ota`;②分支名 `dev-x.y` 与版本主次号不一致时构建失败 | CI 输出明确报错并中断 | P0 | +| F-OT-03 | 「我」页 OTA 组内新增**「开发版本」开关**,默认**关闭** | 关闭=按版本号比较;打开=按 build sequence 比较 | P0 | +| F-OT-04 | 开关值**持久化在设备端**,重启 App 后保持上次选择 | 杀进程重进仍为上次状态 | P0 | +| F-OT-05 | 版本号比较规则:按非数字字符切成数字字段,逐段比较大小(`0.2.0 > 0.1.0`,`0.10 > 0.9`) | 单测覆盖后缀/前缀/缺位/多位段 | P0 | +| F-OT-06 | 切换开关后清空上一次的检查结果,避免误判 | 切换即回到「未检查」状态 | P1 | + +--- + +## 4. 优先级汇总(本期交付范围) + +- **P0(必须)**:F-CK-01~08、F-CK-11;F-CL-01~08;F-LS-01~04;F-SY-01~05;F-OT-01、F-OT-01a、F-OT-02~05;U-01、U-02、U-04、U-05;D-01~05;N-01、N-02、N-04、N-05;E-01、E-02、E-04、E-05、E-08。 +- **P1(应该)**:F-CK-09、10、12~14;F-CL-09;F-LS-05~09;F-SY-06~08;F-OT-06;U-03、U-06~08;D-06;N-03;E-03、E-06、E-07。 +- **P2(可选/延后)**:F-CK-15~17;F-CL-10、11;F-LS-10、11;U-09。 + +--- + +## 5. 里程碑建议 + +| 里程碑 | 内容 | +| --- | --- | +| **M1 数据与接口** | Prisma 模型 + 迁移;`server/routes/checkin.js`、`checklist.js`;周期进度计算与日期查询 | +| **M2 打卡视图** | 环形进度组件 + 打卡卡片列表 + 打卡/撤销/补打 + 完成特效 | +| **M3 日历视图** | 月历网格 + 今天/昨天/明天三视图 + 日期标记 + 日期详情 | +| **M4 清单视图** | 清单 CRUD + 勾选交互 + 进度条 + 模板与复用 | +| **M5 联调打磨** | 真机验证、动效降级、空态/错误态、性能与跨天边界 | + +--- + +## 6. 待确认问题(评审时决策) + +| # | 问题 | 建议 | +| --- | --- | --- | +| 1 | 是否新增第 5 个底部 Tab「打卡」,还是放在首页入口? | ✅ **已决策(2026-10-22)**:「打卡」作为**默认首页放在最左**;原来的「首页」取消,报警/新闻并入宏观 | +| 2 | 周起始日是周一还是周日? | 建议周一(国内习惯) | +| 3 | 是否允许"明天提前打卡"? | 建议**不允许**(F-CK-11),保持统计口径干净 | +| 4 | 删除打卡项时历史是否保留? | 建议默认归档保留,硬删除才清历史 | +| 5 | 预置模板具体包含哪些项? | 评审时列出"出差/回老家"各 8~12 条初始项 | +| 6 | 完成特效是否可全局关闭? | ✅ **已决策(2026-10-22)**:不提供关闭入口,**特效永远打开**;页面内不再有特效开关 | + +--- + +## 附录 A:核心实体草案(供设计阶段参考,非最终表结构) + +```prisma +// 打卡项 +model Habit { + id Int @id @default(autoincrement()) + userId Int + name String + period String // day | week | month | year + target Int @default(1) // 周期内目标次数 n + color String @default("#1677ff") + icon String @default("") + note String @default("") + archived Boolean @default(false) + sortOrder Int @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + records CheckIn[] + + @@index([userId, archived]) +} + +// 打卡记录(一次打卡 = 一条;同日多次为多条) +model CheckIn { + id Int @id @default(autoincrement()) + habitId Int + habit Habit @relation(fields: [habitId], references: [id], onDelete: Cascade) + userId Int + date String // 本地日历日 "YYYY-MM-DD" (Asia/Shanghai) + seq Int @default(1) // 当日第几次 (1..n, 超额继续累加) + note String @default("") + source String @default("user") // user | backfill(补打) + createdAt DateTime @default(now()) + + @@unique([habitId, date, seq]) + @@index([userId, date]) + @@index([habitId, date]) +} + +// 检查清单 +model CheckList { + id Int @id @default(autoincrement()) + userId Int + title String + scene String @default("") // 出差 | 回老家 | 自定义 + note String @default("") + archived Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + items CheckListItem[] + + @@index([userId, archived]) +} + +// 检查项 +model CheckListItem { + id Int @id @default(autoincrement()) + listId Int + list CheckList @relation(fields: [listId], references: [id], onDelete: Cascade) + title String + note String @default("") + done Boolean @default(false) + doneAt DateTime? + sortOrder Int @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([listId, sortOrder]) +} + +// 清单模板(P1) +model CheckListTemplate { + id Int @id @default(autoincrement()) + userId Int? // null = 系统预置 + name String + scene String @default("") + items Json // [{ title, note }] + builtin Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} +``` + +> 说明:周期进度由 `CheckIn` 按周期窗口现场聚合(不冗余计数字段),避免并发与修改 n 后的一致性问题。 + +--- + +## 附录 B:实现约定(开发期补充,用于消除口径歧义) + +| # | 约定 | 说明 | +| --- | --- | --- | +| B-1 | **周期窗口** | 日=自然日;周=周一 00:00 ~ 周日 23:59;月=自然月;年=自然年;时区 Asia/Shanghai | +| B-2 | **环形进度** | 周期完成度 = 周期内累计打卡次数 / n(每周 n 次:打 1 次=1/n 环;每天 1 次:打 1 次直接闭合) | +| B-3 | **日历日期标记** | 用"当日完成度":当日次数 / **日均期望次数**(每天 n 次→n;每周 n 次→ceil(n/7);每月→ceil(n/当月天数);每年→ceil(n/365))。全完成=实心绿点,部分=黄点,超额=紫点,无记录=不显示 | +| B-4 | **streak** | 从当前周期往回数连续达标的周期数;**当前周期未结束且未达标不算断签**(跳过继续往前数) | +| B-5 | **补打限制** | 只能补打最近 7 天(`BACKFILL_DAYS`),未来日期禁止打卡 | +| B-6 | **撤销语义** | 撤销删除本周期内"最近一次"打卡(按 date desc、seq desc 取第一条) | +| B-7 | **删除打卡项** | 默认归档(`archived=true`,历史保留);`?hard=1` 才物理删除并清记录 | +| B-8 | **接口前缀** | 打卡 `/api/checkin/*`、清单 `/api/checklists/*`,全部 `authRequired`,数据按 `userId` 隔离 | +| B-9 | **前端入口** | 底部 Tab 4 个:**打卡(最左,默认选中)** / 宏观 / 监控 / 我;`/` 重定向到 `/checkin`,页内三视图:打卡 / 日历 / 清单。原「首页」取消,报警/新闻并入宏观 | +| B-10 | **特效** | 完成特效 + 触感反馈**恒开**,页面内不提供开关(2026-10-22 变更,原 `localStorage iboard_celebrate` 机制已移除) | +| B-11 | **OTA 版本** | OTA 包 `version = package.json.version + '-ota'`,必须与分支一致(`dev-0.2` → `0.2.0-ota`),CI 有强校验 | +| B-12 | **OTA 判新** | 我的页「开发版本」开关(默认关,持久化 `localStorage.iboard_ota_dev_mode`):关=版本号字段比较,开=build sequence 比较 | + +### 接口清单 + +| 方法 | 路径 | 说明 | +| --- | --- | --- | +| GET | `/api/checkin/habits?date=` | 打卡项列表 + 当前周期进度 + streak | +| POST | `/api/checkin/habits` | 新建打卡项 | +| PUT | `/api/checkin/habits/:id` | 编辑(改 n / 周期 / 归档) | +| DELETE | `/api/checkin/habits/:id?hard=1` | 归档(默认)/ 物理删除 | +| POST | `/api/checkin/habits/:id/check` | 打卡一次(可带 `date` 补打、`note`) | +| DELETE | `/api/checkin/habits/:id/check?date=` | 撤销本周期最近一次 | +| GET | `/api/checkin/day?date=` | 某日详情(今天可打卡 / 昨天可补打 / 明天只读) | +| GET | `/api/checkin/calendar?month=YYYY-MM` | 月历:每日状态 + 当月完成率 | +| GET | `/api/checklists?archived=0\|1` | 清单列表(含进度) | +| POST | `/api/checklists` | 新建清单(可带 `templateId` 从模板生成) | +| GET/PUT/DELETE | `/api/checklists/:id` | 详情 / 编辑 / 删除 | +| POST | `/api/checklists/:id/duplicate` | 复制为新清单(勾选重置) | +| POST | `/api/checklists/:id/items` | 新增检查项 | +| PUT/DELETE | `/api/checklists/items/:id` | 改标题 / 勾选(`done`) / 删除 | +| POST | `/api/checklists/:id/check-all` | 一键全选 / 全不选 | +| GET/POST | `/api/checklists/templates` | 模板列表(含系统预置)/ 另存为模板 | +| DELETE | `/api/checklists/templates/:id` | 删除自定义模板 | diff --git a/generated/prisma/browser.ts b/generated/prisma/browser.ts index 2b1f0eb..6b62810 100644 --- a/generated/prisma/browser.ts +++ b/generated/prisma/browser.ts @@ -107,3 +107,58 @@ export type MihomeEnvSample = Prisma.MihomeEnvSampleModel * */ export type MihomeEnvHour = Prisma.MihomeEnvHourModel +/** + * Model Habit + * + */ +export type Habit = Prisma.HabitModel +/** + * Model CheckIn + * + */ +export type CheckIn = Prisma.CheckInModel +/** + * Model CheckList + * + */ +export type CheckList = Prisma.CheckListModel +/** + * Model CheckListItem + * + */ +export type CheckListItem = Prisma.CheckListItemModel +/** + * Model CheckListTemplate + * + */ +export type CheckListTemplate = Prisma.CheckListTemplateModel +/** + * Model Project + * + */ +export type Project = Prisma.ProjectModel +/** + * Model ProjectMember + * + */ +export type ProjectMember = Prisma.ProjectMemberModel +/** + * Model BoardColumn + * + */ +export type BoardColumn = Prisma.BoardColumnModel +/** + * Model Task + * + */ +export type Task = Prisma.TaskModel +/** + * Model TaskMember + * + */ +export type TaskMember = Prisma.TaskMemberModel +/** + * Model TaskTag + * + */ +export type TaskTag = Prisma.TaskTagModel diff --git a/generated/prisma/client.ts b/generated/prisma/client.ts index 5eb90db..188bf4b 100644 --- a/generated/prisma/client.ts +++ b/generated/prisma/client.ts @@ -131,3 +131,58 @@ export type MihomeEnvSample = Prisma.MihomeEnvSampleModel * */ export type MihomeEnvHour = Prisma.MihomeEnvHourModel +/** + * Model Habit + * + */ +export type Habit = Prisma.HabitModel +/** + * Model CheckIn + * + */ +export type CheckIn = Prisma.CheckInModel +/** + * Model CheckList + * + */ +export type CheckList = Prisma.CheckListModel +/** + * Model CheckListItem + * + */ +export type CheckListItem = Prisma.CheckListItemModel +/** + * Model CheckListTemplate + * + */ +export type CheckListTemplate = Prisma.CheckListTemplateModel +/** + * Model Project + * + */ +export type Project = Prisma.ProjectModel +/** + * Model ProjectMember + * + */ +export type ProjectMember = Prisma.ProjectMemberModel +/** + * Model BoardColumn + * + */ +export type BoardColumn = Prisma.BoardColumnModel +/** + * Model Task + * + */ +export type Task = Prisma.TaskModel +/** + * Model TaskMember + * + */ +export type TaskMember = Prisma.TaskMemberModel +/** + * Model TaskTag + * + */ +export type TaskTag = Prisma.TaskTagModel diff --git a/generated/prisma/commonInputTypes.ts b/generated/prisma/commonInputTypes.ts index d0d5081..3deecf5 100644 --- a/generated/prisma/commonInputTypes.ts +++ b/generated/prisma/commonInputTypes.ts @@ -16,8 +16,8 @@ import type * as Prisma from "./internal/prismaNamespace.ts" export type IntFilter<$PrismaModel = never> = { equals?: number | Prisma.IntFieldRefInput<$PrismaModel> - in?: number[] - notIn?: number[] + in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> + notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> lt?: number | Prisma.IntFieldRefInput<$PrismaModel> lte?: number | Prisma.IntFieldRefInput<$PrismaModel> gt?: number | Prisma.IntFieldRefInput<$PrismaModel> @@ -27,8 +27,8 @@ export type IntFilter<$PrismaModel = never> = { export type StringFilter<$PrismaModel = never> = { equals?: string | Prisma.StringFieldRefInput<$PrismaModel> - in?: string[] - notIn?: string[] + in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> + notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> lt?: string | Prisma.StringFieldRefInput<$PrismaModel> lte?: string | Prisma.StringFieldRefInput<$PrismaModel> gt?: string | Prisma.StringFieldRefInput<$PrismaModel> @@ -36,14 +36,14 @@ export type StringFilter<$PrismaModel = never> = { contains?: string | Prisma.StringFieldRefInput<$PrismaModel> startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> - search?: string + mode?: Prisma.QueryMode not?: Prisma.NestedStringFilter<$PrismaModel> | string } export type DateTimeFilter<$PrismaModel = never> = { equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> - in?: Date[] | string[] - notIn?: Date[] | string[] + in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> + notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> @@ -53,8 +53,8 @@ export type DateTimeFilter<$PrismaModel = never> = { export type IntWithAggregatesFilter<$PrismaModel = never> = { equals?: number | Prisma.IntFieldRefInput<$PrismaModel> - in?: number[] - notIn?: number[] + in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> + notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> lt?: number | Prisma.IntFieldRefInput<$PrismaModel> lte?: number | Prisma.IntFieldRefInput<$PrismaModel> gt?: number | Prisma.IntFieldRefInput<$PrismaModel> @@ -69,8 +69,8 @@ export type IntWithAggregatesFilter<$PrismaModel = never> = { export type StringWithAggregatesFilter<$PrismaModel = never> = { equals?: string | Prisma.StringFieldRefInput<$PrismaModel> - in?: string[] - notIn?: string[] + in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> + notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> lt?: string | Prisma.StringFieldRefInput<$PrismaModel> lte?: string | Prisma.StringFieldRefInput<$PrismaModel> gt?: string | Prisma.StringFieldRefInput<$PrismaModel> @@ -78,7 +78,7 @@ export type StringWithAggregatesFilter<$PrismaModel = never> = { contains?: string | Prisma.StringFieldRefInput<$PrismaModel> startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> - search?: string + mode?: Prisma.QueryMode not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string _count?: Prisma.NestedIntFilter<$PrismaModel> _min?: Prisma.NestedStringFilter<$PrismaModel> @@ -87,8 +87,8 @@ export type StringWithAggregatesFilter<$PrismaModel = never> = { export type DateTimeWithAggregatesFilter<$PrismaModel = never> = { equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> - in?: Date[] | string[] - notIn?: Date[] | string[] + in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> + notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> @@ -108,7 +108,7 @@ export type JsonFilter<$PrismaModel = never> = export type JsonFilterBase<$PrismaModel = never> = { equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter - path?: string + path?: string[] mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel> string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel> string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel> @@ -116,10 +116,10 @@ export type JsonFilterBase<$PrismaModel = never> = { array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null - lt?: runtime.InputJsonValue - lte?: runtime.InputJsonValue - gt?: runtime.InputJsonValue - gte?: runtime.InputJsonValue + lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter } @@ -132,7 +132,7 @@ export type JsonWithAggregatesFilter<$PrismaModel = never> = export type JsonWithAggregatesFilterBase<$PrismaModel = never> = { equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter - path?: string + path?: string[] mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel> string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel> string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel> @@ -140,10 +140,10 @@ export type JsonWithAggregatesFilterBase<$PrismaModel = never> = { array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null - lt?: runtime.InputJsonValue - lte?: runtime.InputJsonValue - gt?: runtime.InputJsonValue - gte?: runtime.InputJsonValue + lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter _count?: Prisma.NestedIntFilter<$PrismaModel> _min?: Prisma.NestedJsonFilter<$PrismaModel> @@ -165,8 +165,8 @@ export type BoolWithAggregatesFilter<$PrismaModel = never> = { export type FloatFilter<$PrismaModel = never> = { equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> - in?: number[] - notIn?: number[] + in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> + notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> @@ -176,8 +176,8 @@ export type FloatFilter<$PrismaModel = never> = { export type FloatWithAggregatesFilter<$PrismaModel = never> = { equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> - in?: number[] - notIn?: number[] + in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> + notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> @@ -192,8 +192,8 @@ export type FloatWithAggregatesFilter<$PrismaModel = never> = { export type StringNullableFilter<$PrismaModel = never> = { equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null - in?: string[] | null - notIn?: string[] | null + in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null + notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null lt?: string | Prisma.StringFieldRefInput<$PrismaModel> lte?: string | Prisma.StringFieldRefInput<$PrismaModel> gt?: string | Prisma.StringFieldRefInput<$PrismaModel> @@ -201,7 +201,7 @@ export type StringNullableFilter<$PrismaModel = never> = { contains?: string | Prisma.StringFieldRefInput<$PrismaModel> startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> - search?: string + mode?: Prisma.QueryMode not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null } @@ -212,8 +212,8 @@ export type SortOrderInput = { export type StringNullableWithAggregatesFilter<$PrismaModel = never> = { equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null - in?: string[] | null - notIn?: string[] | null + in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null + notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null lt?: string | Prisma.StringFieldRefInput<$PrismaModel> lte?: string | Prisma.StringFieldRefInput<$PrismaModel> gt?: string | Prisma.StringFieldRefInput<$PrismaModel> @@ -221,7 +221,7 @@ export type StringNullableWithAggregatesFilter<$PrismaModel = never> = { contains?: string | Prisma.StringFieldRefInput<$PrismaModel> startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> - search?: string + mode?: Prisma.QueryMode not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null _count?: Prisma.NestedIntNullableFilter<$PrismaModel> _min?: Prisma.NestedStringNullableFilter<$PrismaModel> @@ -230,8 +230,8 @@ export type StringNullableWithAggregatesFilter<$PrismaModel = never> = { export type BigIntFilter<$PrismaModel = never> = { equals?: bigint | number | Prisma.BigIntFieldRefInput<$PrismaModel> - in?: bigint[] | number[] - notIn?: bigint[] | number[] + in?: bigint[] | number[] | Prisma.ListBigIntFieldRefInput<$PrismaModel> + notIn?: bigint[] | number[] | Prisma.ListBigIntFieldRefInput<$PrismaModel> lt?: bigint | number | Prisma.BigIntFieldRefInput<$PrismaModel> lte?: bigint | number | Prisma.BigIntFieldRefInput<$PrismaModel> gt?: bigint | number | Prisma.BigIntFieldRefInput<$PrismaModel> @@ -241,8 +241,8 @@ export type BigIntFilter<$PrismaModel = never> = { export type BigIntWithAggregatesFilter<$PrismaModel = never> = { equals?: bigint | number | Prisma.BigIntFieldRefInput<$PrismaModel> - in?: bigint[] | number[] - notIn?: bigint[] | number[] + in?: bigint[] | number[] | Prisma.ListBigIntFieldRefInput<$PrismaModel> + notIn?: bigint[] | number[] | Prisma.ListBigIntFieldRefInput<$PrismaModel> lt?: bigint | number | Prisma.BigIntFieldRefInput<$PrismaModel> lte?: bigint | number | Prisma.BigIntFieldRefInput<$PrismaModel> gt?: bigint | number | Prisma.BigIntFieldRefInput<$PrismaModel> @@ -264,7 +264,7 @@ export type JsonNullableFilter<$PrismaModel = never> = export type JsonNullableFilterBase<$PrismaModel = never> = { equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter - path?: string + path?: string[] mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel> string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel> string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel> @@ -272,10 +272,10 @@ export type JsonNullableFilterBase<$PrismaModel = never> = { array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null - lt?: runtime.InputJsonValue - lte?: runtime.InputJsonValue - gt?: runtime.InputJsonValue - gte?: runtime.InputJsonValue + lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter } @@ -288,7 +288,7 @@ export type JsonNullableWithAggregatesFilter<$PrismaModel = never> = export type JsonNullableWithAggregatesFilterBase<$PrismaModel = never> = { equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter - path?: string + path?: string[] mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel> string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel> string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel> @@ -296,10 +296,10 @@ export type JsonNullableWithAggregatesFilterBase<$PrismaModel = never> = { array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null - lt?: runtime.InputJsonValue - lte?: runtime.InputJsonValue - gt?: runtime.InputJsonValue - gte?: runtime.InputJsonValue + lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter _count?: Prisma.NestedIntNullableFilter<$PrismaModel> _min?: Prisma.NestedJsonNullableFilter<$PrismaModel> @@ -308,8 +308,8 @@ export type JsonNullableWithAggregatesFilterBase<$PrismaModel = never> = { export type IntNullableFilter<$PrismaModel = never> = { equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null - in?: number[] | null - notIn?: number[] | null + in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null + notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null lt?: number | Prisma.IntFieldRefInput<$PrismaModel> lte?: number | Prisma.IntFieldRefInput<$PrismaModel> gt?: number | Prisma.IntFieldRefInput<$PrismaModel> @@ -319,8 +319,8 @@ export type IntNullableFilter<$PrismaModel = never> = { export type DateTimeNullableFilter<$PrismaModel = never> = { equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null - in?: Date[] | string[] | null - notIn?: Date[] | string[] | null + in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null + notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> @@ -330,8 +330,8 @@ export type DateTimeNullableFilter<$PrismaModel = never> = { export type IntNullableWithAggregatesFilter<$PrismaModel = never> = { equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null - in?: number[] | null - notIn?: number[] | null + in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null + notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null lt?: number | Prisma.IntFieldRefInput<$PrismaModel> lte?: number | Prisma.IntFieldRefInput<$PrismaModel> gt?: number | Prisma.IntFieldRefInput<$PrismaModel> @@ -346,8 +346,8 @@ export type IntNullableWithAggregatesFilter<$PrismaModel = never> = { export type DateTimeNullableWithAggregatesFilter<$PrismaModel = never> = { equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null - in?: Date[] | string[] | null - notIn?: Date[] | string[] | null + in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null + notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> @@ -360,8 +360,8 @@ export type DateTimeNullableWithAggregatesFilter<$PrismaModel = never> = { export type FloatNullableFilter<$PrismaModel = never> = { equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null - in?: number[] | null - notIn?: number[] | null + in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null + notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> @@ -371,8 +371,8 @@ export type FloatNullableFilter<$PrismaModel = never> = { export type FloatNullableWithAggregatesFilter<$PrismaModel = never> = { equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null - in?: number[] | null - notIn?: number[] | null + in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null + notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> @@ -387,8 +387,8 @@ export type FloatNullableWithAggregatesFilter<$PrismaModel = never> = { export type NestedIntFilter<$PrismaModel = never> = { equals?: number | Prisma.IntFieldRefInput<$PrismaModel> - in?: number[] - notIn?: number[] + in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> + notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> lt?: number | Prisma.IntFieldRefInput<$PrismaModel> lte?: number | Prisma.IntFieldRefInput<$PrismaModel> gt?: number | Prisma.IntFieldRefInput<$PrismaModel> @@ -398,8 +398,8 @@ export type NestedIntFilter<$PrismaModel = never> = { export type NestedStringFilter<$PrismaModel = never> = { equals?: string | Prisma.StringFieldRefInput<$PrismaModel> - in?: string[] - notIn?: string[] + in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> + notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> lt?: string | Prisma.StringFieldRefInput<$PrismaModel> lte?: string | Prisma.StringFieldRefInput<$PrismaModel> gt?: string | Prisma.StringFieldRefInput<$PrismaModel> @@ -407,14 +407,13 @@ export type NestedStringFilter<$PrismaModel = never> = { contains?: string | Prisma.StringFieldRefInput<$PrismaModel> startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> - search?: string not?: Prisma.NestedStringFilter<$PrismaModel> | string } export type NestedDateTimeFilter<$PrismaModel = never> = { equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> - in?: Date[] | string[] - notIn?: Date[] | string[] + in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> + notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> @@ -424,8 +423,8 @@ export type NestedDateTimeFilter<$PrismaModel = never> = { export type NestedIntWithAggregatesFilter<$PrismaModel = never> = { equals?: number | Prisma.IntFieldRefInput<$PrismaModel> - in?: number[] - notIn?: number[] + in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> + notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> lt?: number | Prisma.IntFieldRefInput<$PrismaModel> lte?: number | Prisma.IntFieldRefInput<$PrismaModel> gt?: number | Prisma.IntFieldRefInput<$PrismaModel> @@ -440,8 +439,8 @@ export type NestedIntWithAggregatesFilter<$PrismaModel = never> = { export type NestedFloatFilter<$PrismaModel = never> = { equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> - in?: number[] - notIn?: number[] + in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> + notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> @@ -451,8 +450,8 @@ export type NestedFloatFilter<$PrismaModel = never> = { export type NestedStringWithAggregatesFilter<$PrismaModel = never> = { equals?: string | Prisma.StringFieldRefInput<$PrismaModel> - in?: string[] - notIn?: string[] + in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> + notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> lt?: string | Prisma.StringFieldRefInput<$PrismaModel> lte?: string | Prisma.StringFieldRefInput<$PrismaModel> gt?: string | Prisma.StringFieldRefInput<$PrismaModel> @@ -460,7 +459,6 @@ export type NestedStringWithAggregatesFilter<$PrismaModel = never> = { contains?: string | Prisma.StringFieldRefInput<$PrismaModel> startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> - search?: string not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string _count?: Prisma.NestedIntFilter<$PrismaModel> _min?: Prisma.NestedStringFilter<$PrismaModel> @@ -469,8 +467,8 @@ export type NestedStringWithAggregatesFilter<$PrismaModel = never> = { export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = { equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> - in?: Date[] | string[] - notIn?: Date[] | string[] + in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> + notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> @@ -490,7 +488,7 @@ export type NestedJsonFilter<$PrismaModel = never> = export type NestedJsonFilterBase<$PrismaModel = never> = { equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter - path?: string + path?: string[] mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel> string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel> string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel> @@ -498,10 +496,10 @@ export type NestedJsonFilterBase<$PrismaModel = never> = { array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null - lt?: runtime.InputJsonValue - lte?: runtime.InputJsonValue - gt?: runtime.InputJsonValue - gte?: runtime.InputJsonValue + lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter } @@ -520,8 +518,8 @@ export type NestedBoolWithAggregatesFilter<$PrismaModel = never> = { export type NestedFloatWithAggregatesFilter<$PrismaModel = never> = { equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> - in?: number[] - notIn?: number[] + in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> + notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> @@ -536,8 +534,8 @@ export type NestedFloatWithAggregatesFilter<$PrismaModel = never> = { export type NestedStringNullableFilter<$PrismaModel = never> = { equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null - in?: string[] | null - notIn?: string[] | null + in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null + notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null lt?: string | Prisma.StringFieldRefInput<$PrismaModel> lte?: string | Prisma.StringFieldRefInput<$PrismaModel> gt?: string | Prisma.StringFieldRefInput<$PrismaModel> @@ -545,14 +543,13 @@ export type NestedStringNullableFilter<$PrismaModel = never> = { contains?: string | Prisma.StringFieldRefInput<$PrismaModel> startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> - search?: string not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null } export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = { equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null - in?: string[] | null - notIn?: string[] | null + in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null + notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null lt?: string | Prisma.StringFieldRefInput<$PrismaModel> lte?: string | Prisma.StringFieldRefInput<$PrismaModel> gt?: string | Prisma.StringFieldRefInput<$PrismaModel> @@ -560,7 +557,6 @@ export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = { contains?: string | Prisma.StringFieldRefInput<$PrismaModel> startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> - search?: string not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null _count?: Prisma.NestedIntNullableFilter<$PrismaModel> _min?: Prisma.NestedStringNullableFilter<$PrismaModel> @@ -569,8 +565,8 @@ export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = { export type NestedIntNullableFilter<$PrismaModel = never> = { equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null - in?: number[] | null - notIn?: number[] | null + in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null + notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null lt?: number | Prisma.IntFieldRefInput<$PrismaModel> lte?: number | Prisma.IntFieldRefInput<$PrismaModel> gt?: number | Prisma.IntFieldRefInput<$PrismaModel> @@ -580,8 +576,8 @@ export type NestedIntNullableFilter<$PrismaModel = never> = { export type NestedBigIntFilter<$PrismaModel = never> = { equals?: bigint | number | Prisma.BigIntFieldRefInput<$PrismaModel> - in?: bigint[] | number[] - notIn?: bigint[] | number[] + in?: bigint[] | number[] | Prisma.ListBigIntFieldRefInput<$PrismaModel> + notIn?: bigint[] | number[] | Prisma.ListBigIntFieldRefInput<$PrismaModel> lt?: bigint | number | Prisma.BigIntFieldRefInput<$PrismaModel> lte?: bigint | number | Prisma.BigIntFieldRefInput<$PrismaModel> gt?: bigint | number | Prisma.BigIntFieldRefInput<$PrismaModel> @@ -591,8 +587,8 @@ export type NestedBigIntFilter<$PrismaModel = never> = { export type NestedBigIntWithAggregatesFilter<$PrismaModel = never> = { equals?: bigint | number | Prisma.BigIntFieldRefInput<$PrismaModel> - in?: bigint[] | number[] - notIn?: bigint[] | number[] + in?: bigint[] | number[] | Prisma.ListBigIntFieldRefInput<$PrismaModel> + notIn?: bigint[] | number[] | Prisma.ListBigIntFieldRefInput<$PrismaModel> lt?: bigint | number | Prisma.BigIntFieldRefInput<$PrismaModel> lte?: bigint | number | Prisma.BigIntFieldRefInput<$PrismaModel> gt?: bigint | number | Prisma.BigIntFieldRefInput<$PrismaModel> @@ -614,7 +610,7 @@ export type NestedJsonNullableFilter<$PrismaModel = never> = export type NestedJsonNullableFilterBase<$PrismaModel = never> = { equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter - path?: string + path?: string[] mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel> string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel> string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel> @@ -622,17 +618,17 @@ export type NestedJsonNullableFilterBase<$PrismaModel = never> = { array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null - lt?: runtime.InputJsonValue - lte?: runtime.InputJsonValue - gt?: runtime.InputJsonValue - gte?: runtime.InputJsonValue + lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> + gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter } export type NestedDateTimeNullableFilter<$PrismaModel = never> = { equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null - in?: Date[] | string[] | null - notIn?: Date[] | string[] | null + in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null + notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> @@ -642,8 +638,8 @@ export type NestedDateTimeNullableFilter<$PrismaModel = never> = { export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = { equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null - in?: number[] | null - notIn?: number[] | null + in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null + notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null lt?: number | Prisma.IntFieldRefInput<$PrismaModel> lte?: number | Prisma.IntFieldRefInput<$PrismaModel> gt?: number | Prisma.IntFieldRefInput<$PrismaModel> @@ -658,8 +654,8 @@ export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = { export type NestedFloatNullableFilter<$PrismaModel = never> = { equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null - in?: number[] | null - notIn?: number[] | null + in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null + notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> @@ -669,8 +665,8 @@ export type NestedFloatNullableFilter<$PrismaModel = never> = { export type NestedDateTimeNullableWithAggregatesFilter<$PrismaModel = never> = { equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null - in?: Date[] | string[] | null - notIn?: Date[] | string[] | null + in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null + notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> @@ -683,8 +679,8 @@ export type NestedDateTimeNullableWithAggregatesFilter<$PrismaModel = never> = { export type NestedFloatNullableWithAggregatesFilter<$PrismaModel = never> = { equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null - in?: number[] | null - notIn?: number[] | null + in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null + notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> diff --git a/generated/prisma/internal/class.ts b/generated/prisma/internal/class.ts index 9d85193..c64d7b8 100644 --- a/generated/prisma/internal/class.ts +++ b/generated/prisma/internal/class.ts @@ -19,8 +19,8 @@ const config: runtime.GetPrismaClientConfig = { "previewFeatures": [], "clientVersion": "7.7.0", "engineVersion": "75cbdc1eb7150937890ad5465d861175c6624711", - "activeProvider": "mysql", - "inlineSchema": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\n// Get a free hosted Postgres database in seconds: `npx create-db`\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"mysql\"\n}\n\nmodel User {\n id Int @id @default(autoincrement())\n name String\n email String @unique\n password String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Article {\n id Int @id @default(autoincrement())\n title String\n slug String @unique\n content String\n date String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Strategy {\n id Int @id @default(autoincrement())\n name String\n description String\n data Json\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Indicator {\n id Int @id @default(autoincrement())\n name String @unique\n groupName String\n title String @default(\"\")\n unit String @default(\"\")\n source String @default(\"\")\n frequency String @default(\"month\") // year | quarter | month | day\n hidden Boolean @default(false) // true = 数据/脚本保留, 但不在前端展示\n points IndicatorPoint[]\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel IndicatorPoint {\n id Int @id @default(autoincrement())\n indicatorId Int\n indicator Indicator @relation(fields: [indicatorId], references: [id], onDelete: Cascade)\n period String // \"2024\" | \"2024-Q1\" | \"2024-01\" | \"2024-01-15\"\n value Float\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@unique([indicatorId, period])\n}\n\n// 新闻: 每天 3 点由 mx_finance_search_news 拉取, 按影响大小排序, 累加多日供首页无限滚动\nmodel News {\n id Int @id @default(autoincrement())\n title String\n summary String @default(\"\")\n content String? @db.Text\n source String @default(\"\")\n url String @default(\"\")\n publishedAt DateTime\n impactRank Int\n createdAt DateTime @default(now())\n\n @@unique([title, publishedAt])\n @@index([publishedAt])\n}\n\n// 报警: refresh 写库时比对同 (indicatorName, period) 的跨源数据, 差异超阈值记录\nmodel Alert {\n id Int @id @default(autoincrement())\n indicatorName String\n period String\n valueA Float\n valueB Float\n sourceA String\n sourceB String\n diff Float\n diffRatio Float\n createdAt DateTime @default(now())\n\n @@index([createdAt])\n}\n\n// 服务器监控: 对接 komari 公开 API\nmodel ServerNode {\n id Int @id @default(autoincrement())\n uuid String @unique\n name String\n cpuName String @default(\"\")\n os String @default(\"\")\n region String @default(\"\")\n memTotal BigInt @default(0)\n diskTotal BigInt @default(0)\n cpuCores Int @default(0)\n // 最新实时快照\n cpuUsage Float @default(0)\n ramUsed BigInt @default(0)\n swapUsed BigInt @default(0)\n load1 Float @default(0)\n load5 Float @default(0)\n load15 Float @default(0)\n diskUsed BigInt @default(0)\n netIn BigInt @default(0)\n netOut BigInt @default(0)\n uptime BigInt @default(0)\n process Int @default(0)\n connections Int @default(0)\n status String @default(\"unknown\") // online | offline | unknown\n recordedAt DateTime\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\n// 时序历史: 每节点每分钟一条 (komari records 1 分钟粒度)\nmodel ServerNodeHistory {\n id Int @id @default(autoincrement())\n uuid String\n period DateTime\n cpu Float @default(0)\n ram BigInt @default(0)\n load Float @default(0)\n disk BigInt @default(0)\n netIn BigInt @default(0)\n netOut BigInt @default(0)\n process Int @default(0)\n connections Int @default(0)\n createdAt DateTime @default(now())\n\n @@unique([uuid, period])\n @@index([uuid, period])\n}\n\n// 服务健康检查: 本应用自检 (健康端点 / DB / 第三方)\nmodel ServiceCheck {\n id Int @id @default(autoincrement())\n name String @unique\n url String\n status String @default(\"unknown\") // up | down | unknown\n latencyMs Int @default(0)\n message String @default(\"\")\n lastCheckAt DateTime\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Econ_SowInventory {\n id Int @id @default(autoincrement())\n month String @unique\n inventory Int // 能繁母猪数量(万头)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel ScraperCache {\n id Int @id @default(autoincrement())\n scraperName String @unique // 爬虫名称,如:sow-inventory\n lastDataHash String? // 上次数据指纹\n lastUpdateAt DateTime // 上次更新时间\n metadata Json? // 其他元数据\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\n// 浙江省油价历史 (apizero price 接口每日抓取)\n// 每天 04:00 cron 触发: 检查 schedule 里待定且 <= 昨天的调价日, 抓 price?province=浙江 存本表\n// unique (date, province, fuel) 防重复\nmodel OilPriceHistory {\n id Int @id @default(autoincrement())\n date DateTime // 调价日次日 (即数据实际抓取日)\n province String // '浙江'\n fuel String // '92' | '95' | '0'\n price Float // 油价 (元/升, 4 位小数足够)\n source String @default(\"apizero\")\n createdAt DateTime @default(now())\n\n @@unique([date, province, fuel])\n @@index([province, fuel, date])\n}\n\n// ==================== 环境监控:米家 ====================\n\n// 米家授权状态 (DiskCache 存凭证, 本表只存状态/错误供前端展示)\nmodel MihomeAccount {\n id Int @id @default(autoincrement())\n key String @unique @default(\"default\")\n userId String @default(\"\")\n status String @default(\"unknown\") // ok | pending | expired\n lastError String @default(\"\")\n updatedAt DateTime @updatedAt\n}\n\nmodel MihomeHome {\n id Int @id @default(autoincrement())\n homeId String @unique\n name String\n createdAt DateTime @default(now())\n devices MihomeDevice[]\n}\n\nmodel MihomeDevice {\n id Int @id @default(autoincrement())\n did String @unique\n homeId String\n home MihomeHome @relation(fields: [homeId], references: [homeId])\n model String\n name String\n alias String? // 用户别名, 仅本应用展示\n tempSiid Int\n tempPiid Int\n humSiid Int\n humPiid Int\n battSiid Int // 电池电量属性 (炎米 3.1003 / 青萍 2.3)\n battPiid Int\n battery Int? // 最近一次电量 (每天读一次, 标题展示用)\n batteryAt DateTime? // 电量读取时间 (当天缓存, 不频繁更新)\n enabled Boolean @default(true)\n lastSyncAt DateTime?\n createdAt DateTime @default(now())\n samples MihomeEnvSample[]\n hours MihomeEnvHour[]\n}\n\n// 日档: 月视图(逐日)数据源; unique (deviceId, date) 防重\nmodel MihomeEnvSample {\n id Int @id @default(autoincrement())\n deviceId Int\n device MihomeDevice @relation(fields: [deviceId], references: [id], onDelete: Cascade)\n date DateTime // 采样日 (Asia/Shanghai 当日 00:00 存 UTC)\n temperature Float? // 当日最后一次实时值 (或 vendor hour 均值)\n humidity Float?\n temperatureAvg Float? // 当日均值 (由小时档聚合, 或 vendor_hour)\n humidityAvg Float?\n temperatureMin Float? // 当日最低\n temperatureMax Float? // 当日最高\n humidityMin Float?\n humidityMax Float?\n origin String @default(\"live\") // live | vendor_history | backfill\n sampledAt DateTime?\n raw Json?\n createdAt DateTime @default(now())\n\n @@unique([deviceId, date])\n @@index([deviceId, date])\n}\n\n// 小时档: 当天视图(逐小时)数据源; unique (deviceId, hour) 防重\nmodel MihomeEnvHour {\n id Int @id @default(autoincrement())\n deviceId Int\n device MihomeDevice @relation(fields: [deviceId], references: [id], onDelete: Cascade)\n hour DateTime // Asia/Shanghai 整点 (存 UTC)\n temperatureAvg Float? // 该小时均值 (live 采样点, 或 vendor_hour)\n humidityAvg Float?\n raw Json?\n createdAt DateTime @default(now())\n\n @@unique([deviceId, hour])\n @@index([deviceId, hour])\n}\n", + "activeProvider": "postgresql", + "inlineSchema": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\n// Get a free hosted Postgres database in seconds: `npx create-db`\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"../generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n}\n\nmodel User {\n id Int @id @default(autoincrement())\n name String\n email String @unique\n password String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n // V0.2 打卡 / 清单: 用户删除时级联清理\n habits Habit[]\n checkIns CheckIn[]\n checkLists CheckList[]\n templates CheckListTemplate[]\n\n // V0.2 任务模块\n ownedProjects Project[] @relation(\"ProjectOwner\")\n projectMembers ProjectMember[]\n assignedTasks Task[] @relation(\"TaskAssignee\")\n createdTasks Task[] @relation(\"TaskCreator\")\n taskMembers TaskMember[]\n}\n\nmodel Article {\n id Int @id @default(autoincrement())\n title String\n slug String @unique\n content String\n date String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Strategy {\n id Int @id @default(autoincrement())\n name String\n description String\n data Json\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Indicator {\n id Int @id @default(autoincrement())\n name String @unique\n groupName String\n title String @default(\"\")\n unit String @default(\"\")\n source String @default(\"\")\n frequency String @default(\"month\") // year | quarter | month | day\n hidden Boolean @default(false) // true = 数据/脚本保留, 但不在前端展示\n points IndicatorPoint[]\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel IndicatorPoint {\n id Int @id @default(autoincrement())\n indicatorId Int\n indicator Indicator @relation(fields: [indicatorId], references: [id], onDelete: Cascade)\n period String // \"2024\" | \"2024-Q1\" | \"2024-01\" | \"2024-01-15\"\n value Float\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@unique([indicatorId, period])\n}\n\n// 新闻: 每天 3 点由 mx_finance_search_news 拉取, 按影响大小排序, 累加多日供首页无限滚动\nmodel News {\n id Int @id @default(autoincrement())\n title String\n summary String @default(\"\")\n content String? @db.Text\n source String @default(\"\")\n url String @default(\"\")\n publishedAt DateTime\n impactRank Int\n createdAt DateTime @default(now())\n\n @@unique([title, publishedAt])\n @@index([publishedAt])\n}\n\n// 报警: refresh 写库时比对同 (indicatorName, period) 的跨源数据, 差异超阈值记录\nmodel Alert {\n id Int @id @default(autoincrement())\n indicatorName String\n period String\n valueA Float\n valueB Float\n sourceA String\n sourceB String\n diff Float\n diffRatio Float\n createdAt DateTime @default(now())\n\n @@index([createdAt])\n}\n\n// 服务器监控: 对接 komari 公开 API\nmodel ServerNode {\n id Int @id @default(autoincrement())\n uuid String @unique\n name String\n cpuName String @default(\"\")\n os String @default(\"\")\n region String @default(\"\")\n memTotal BigInt @default(0)\n diskTotal BigInt @default(0)\n cpuCores Int @default(0)\n // 最新实时快照\n cpuUsage Float @default(0)\n ramUsed BigInt @default(0)\n swapUsed BigInt @default(0)\n load1 Float @default(0)\n load5 Float @default(0)\n load15 Float @default(0)\n diskUsed BigInt @default(0)\n netIn BigInt @default(0)\n netOut BigInt @default(0)\n uptime BigInt @default(0)\n process Int @default(0)\n connections Int @default(0)\n status String @default(\"unknown\") // online | offline | unknown\n recordedAt DateTime\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\n// 时序历史: 每节点每分钟一条 (komari records 1 分钟粒度)\nmodel ServerNodeHistory {\n id Int @id @default(autoincrement())\n uuid String\n period DateTime\n cpu Float @default(0)\n ram BigInt @default(0)\n load Float @default(0)\n disk BigInt @default(0)\n netIn BigInt @default(0)\n netOut BigInt @default(0)\n process Int @default(0)\n connections Int @default(0)\n createdAt DateTime @default(now())\n\n @@unique([uuid, period])\n @@index([uuid, period])\n}\n\n// 服务健康检查: 本应用自检 (健康端点 / DB / 第三方)\nmodel ServiceCheck {\n id Int @id @default(autoincrement())\n name String @unique\n url String\n status String @default(\"unknown\") // up | down | unknown\n latencyMs Int @default(0)\n message String @default(\"\")\n lastCheckAt DateTime\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel Econ_SowInventory {\n id Int @id @default(autoincrement())\n month String @unique\n inventory Int // 能繁母猪数量(万头)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\nmodel ScraperCache {\n id Int @id @default(autoincrement())\n scraperName String @unique // 爬虫名称,如:sow-inventory\n lastDataHash String? // 上次数据指纹\n lastUpdateAt DateTime // 上次更新时间\n metadata Json? // 其他元数据\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n\n// 浙江省油价历史 (apizero price 接口每日抓取)\n// 每天 04:00 cron 触发: 检查 schedule 里待定且 <= 昨天的调价日, 抓 price?province=浙江 存本表\n// unique (date, province, fuel) 防重复\nmodel OilPriceHistory {\n id Int @id @default(autoincrement())\n date DateTime // 调价日次日 (即数据实际抓取日)\n province String // '浙江'\n fuel String // '92' | '95' | '0'\n price Float // 油价 (元/升, 4 位小数足够)\n source String @default(\"apizero\")\n createdAt DateTime @default(now())\n\n @@unique([date, province, fuel])\n @@index([province, fuel, date])\n}\n\n// ==================== 环境监控:米家 ====================\n\n// 米家授权状态 (DiskCache 存凭证, 本表只存状态/错误供前端展示)\nmodel MihomeAccount {\n id Int @id @default(autoincrement())\n key String @unique @default(\"default\")\n userId String @default(\"\")\n status String @default(\"unknown\") // ok | pending | expired\n lastError String @default(\"\")\n updatedAt DateTime @updatedAt\n}\n\nmodel MihomeHome {\n id Int @id @default(autoincrement())\n homeId String @unique\n name String\n createdAt DateTime @default(now())\n devices MihomeDevice[]\n}\n\nmodel MihomeDevice {\n id Int @id @default(autoincrement())\n did String @unique\n homeId String\n home MihomeHome @relation(fields: [homeId], references: [homeId])\n model String\n name String\n alias String? // 用户别名, 仅本应用展示\n tempSiid Int\n tempPiid Int\n humSiid Int\n humPiid Int\n battSiid Int // 电池电量属性 (炎米 3.1003 / 青萍 2.3)\n battPiid Int\n battery Int? // 最近一次电量 (每天读一次, 标题展示用)\n batteryAt DateTime? // 电量读取时间 (当天缓存, 不频繁更新)\n enabled Boolean @default(true)\n lastSyncAt DateTime?\n createdAt DateTime @default(now())\n samples MihomeEnvSample[]\n hours MihomeEnvHour[]\n}\n\n// 日档: 月视图(逐日)数据源; unique (deviceId, date) 防重\nmodel MihomeEnvSample {\n id Int @id @default(autoincrement())\n deviceId Int\n device MihomeDevice @relation(fields: [deviceId], references: [id], onDelete: Cascade)\n date DateTime // 采样日 (Asia/Shanghai 当日 00:00 存 UTC)\n temperature Float? // 当日最后一次实时值 (或 vendor hour 均值)\n humidity Float?\n temperatureAvg Float? // 当日均值 (由小时档聚合, 或 vendor_hour)\n humidityAvg Float?\n temperatureMin Float? // 当日最低\n temperatureMax Float? // 当日最高\n humidityMin Float?\n humidityMax Float?\n origin String @default(\"live\") // live | vendor_history | backfill\n sampledAt DateTime?\n raw Json?\n createdAt DateTime @default(now())\n\n @@unique([deviceId, date])\n @@index([deviceId, date])\n}\n\n// 小时档: 当天视图(逐小时)数据源; unique (deviceId, hour) 防重\nmodel MihomeEnvHour {\n id Int @id @default(autoincrement())\n deviceId Int\n device MihomeDevice @relation(fields: [deviceId], references: [id], onDelete: Cascade)\n hour DateTime // Asia/Shanghai 整点 (存 UTC)\n temperatureAvg Float? // 该小时均值 (live 采样点, 或 vendor_hour)\n humidityAvg Float?\n raw Json?\n createdAt DateTime @default(now())\n\n @@unique([deviceId, hour])\n @@index([deviceId, hour])\n}\n\n// ==================== 打卡 / 日历 / 清单 (V0.2) ====================\n\n// 打卡项: 周期性目标 (每天 / 每周 / 每月 / 每年 n 次)\nmodel Habit {\n id Int @id @default(autoincrement())\n userId Int\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n name String\n period String @default(\"day\") // day | week | month | year\n target Int @default(1) // 周期内目标次数 n (>=1)\n color String @default(\"#1677ff\")\n icon String @default(\"\") // emoji 或图标名, 预留\n note String @default(\"\")\n archived Boolean @default(false) // 归档: 不出现在今日待办, 历史保留\n sortOrder Int @default(0)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n records CheckIn[]\n\n @@index([userId, archived, sortOrder])\n}\n\n// 打卡记录: 一次打卡 = 一条; 同日多次 => seq 递增 (1..n, 超额继续累加)\nmodel CheckIn {\n id Int @id @default(autoincrement())\n habitId Int\n habit Habit @relation(fields: [habitId], references: [id], onDelete: Cascade)\n userId Int\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n date String @db.VarChar(10) // 本地日历日 \"YYYY-MM-DD\" (Asia/Shanghai)\n seq Int @default(1) // 当日第几次\n note String @default(\"\")\n source String @default(\"user\") // user | backfill (补打)\n createdAt DateTime @default(now())\n\n @@unique([habitId, date, seq])\n @@index([userId, date])\n @@index([habitId, date])\n}\n\n// 检查清单: 出差 / 回老家 等场景的一次性清单\nmodel CheckList {\n id Int @id @default(autoincrement())\n userId Int\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n title String\n scene String @default(\"\") // 出差 | 回老家 | 自定义\n note String @default(\"\")\n archived Boolean @default(false)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n items CheckListItem[]\n\n @@index([userId, archived])\n}\n\n// 检查项: 勾选即完成\nmodel CheckListItem {\n id Int @id @default(autoincrement())\n listId Int\n list CheckList @relation(fields: [listId], references: [id], onDelete: Cascade)\n title String\n note String @default(\"\")\n done Boolean @default(false)\n doneAt DateTime?\n sortOrder Int @default(0)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([listId, sortOrder])\n}\n\n// 清单模板: builtin = 系统预置 (userId 为空); 否则为用户自定义\nmodel CheckListTemplate {\n id Int @id @default(autoincrement())\n userId Int?\n user User? @relation(fields: [userId], references: [id], onDelete: Cascade)\n name String\n scene String @default(\"\")\n items Json // [{ title, note }]\n builtin Boolean @default(false)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n @@index([userId, builtin])\n}\n\n// ================== 任务模块 (V0.2 任务模块需求文档 T-*) ==================\n// 口径见 docs/V0.2/任务模块/任务模块数据库设计文档.md\n// 项目 = 隔离 + 权限单位; 看板列是实体(可增删改排序);\n// 任务状态(todo/doing/done) 与 看板列 是两个正交维度, 由列的 boundStatus 决定是否联动.\n\nmodel Project {\n id Int @id @default(autoincrement())\n uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑, 见 docs/V0.2/离线编辑模块)\n code String @unique @db.VarChar(8) // 项目短编号: 用户自拟 2~8 位前缀, 全局唯一, 服务端分配\n name String @db.VarChar(50)\n ownerId Int\n owner User @relation(\"ProjectOwner\", fields: [ownerId], references: [id], onDelete: Cascade)\n isPersonal Boolean @default(false)\n archived Boolean @default(false)\n notes Json @default(\"[]\") // 项目信息块 [{ title, content }] (T-PJ-13)\n taskKinds Json? // 任务类型标签列表 (未配置时服务端回落默认四类)\n icon String? @db.Text // 项目图标 (data URL, 客户端裁剪为 96x96 JPEG)\n autoArchive String @default(\"never\") @db.VarChar(8) // 自动归档档位: 3d/1w/1m/never (T-ARC-02)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n members ProjectMember[]\n columns BoardColumn[]\n tasks Task[]\n\n @@index([ownerId])\n @@index([archived])\n}\n\nmodel ProjectMember {\n id Int @id @default(autoincrement())\n uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑)\n projectUuid String? @db.VarChar(36) // 外键 uuid 镜像 (触发器维护, 库级 NOT NULL)\n projectId Int\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n userId Int\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n permission String @default(\"member\")\n createdAt DateTime @default(now())\n\n @@unique([projectId, userId])\n @@index([userId])\n}\n\nmodel BoardColumn {\n id Int @id @default(autoincrement())\n uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑)\n projectUuid String? @db.VarChar(36) // projectId 的 uuid 镜像 (触发器维护, 库级 NOT NULL; Prisma 侧可空以便触发器填充)\n projectId Int\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n name String @db.VarChar(20)\n sortOrder Int @default(0)\n boundStatus String? @db.VarChar(8)\n retention String @default(\"forever\") @db.VarChar(8)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n tasks Task[]\n\n @@unique([projectId, name])\n @@index([projectId, sortOrder])\n}\n\nmodel Task {\n id Int @id @default(autoincrement())\n uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑)\n projectUuid String? @db.VarChar(36) // 外键 uuid 镜像 (触发器维护, 库级 NOT NULL)\n columnUuid String? @db.VarChar(36)\n parentUuid String? @db.VarChar(36)\n projectId Int\n project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)\n columnId Int\n column BoardColumn @relation(fields: [columnId], references: [id], onDelete: Restrict)\n parentId Int?\n parent Task? @relation(\"Subtask\", fields: [parentId], references: [id], onDelete: Cascade)\n children Task[] @relation(\"Subtask\")\n\n title String @db.VarChar(200)\n description String @default(\"\") @db.Text\n kind String? @db.VarChar(20) // 任务类型标签 (项目 taskKinds 之一)\n markDate DateTime? // 时间标记 (完整日期存储, 展示为 MM-dd)\n status String @default(\"todo\") @db.VarChar(8)\n doneAt DateTime?\n\n assigneeId Int?\n assignee User? @relation(\"TaskAssignee\", fields: [assigneeId], references: [id], onDelete: SetNull)\n creatorId Int\n creator User @relation(\"TaskCreator\", fields: [creatorId], references: [id], onDelete: Cascade)\n\n extras Json @default(\"[]\")\n sortOrder Int @default(0)\n archived Boolean @default(false)\n archivedAt DateTime?\n archiveSource String? @db.VarChar(8) // manual | auto (T-ARC)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n members TaskMember[]\n tags TaskTag[]\n\n number Int? // 项目内自增编号 (服务端分配, 展示为 `code-number`); 本地未定号的离线新建任务为空\n\n @@unique([projectId, number])\n @@index([projectId, columnId])\n @@index([projectId, status])\n @@index([parentId])\n @@index([projectId, doneAt])\n @@index([projectId, archived, archivedAt])\n @@index([projectId, status, archived])\n}\n\nmodel TaskMember {\n id Int @id @default(autoincrement())\n uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑)\n taskUuid String? @db.VarChar(36) // 外键 uuid 镜像 (触发器维护, 库级 NOT NULL)\n taskId Int\n task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)\n userId Int\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@unique([taskId, userId])\n}\n\nmodel TaskTag {\n id Int @id @default(autoincrement())\n uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑)\n taskUuid String? @db.VarChar(36) // 外键 uuid 镜像 (触发器维护, 库级 NOT NULL)\n taskId Int\n task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)\n name String @db.VarChar(20)\n\n @@index([taskId])\n}\n", "runtimeDataModel": { "models": {}, "enums": {}, @@ -32,10 +32,10 @@ const config: runtime.GetPrismaClientConfig = { } } -config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Article\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"slug\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"content\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Strategy\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"data\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Indicator\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"groupName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"unit\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"source\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"frequency\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"hidden\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"points\",\"kind\":\"object\",\"type\":\"IndicatorPoint\",\"relationName\":\"IndicatorToIndicatorPoint\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"IndicatorPoint\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"indicatorId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"indicator\",\"kind\":\"object\",\"type\":\"Indicator\",\"relationName\":\"IndicatorToIndicatorPoint\"},{\"name\":\"period\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"value\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"News\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"summary\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"content\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"source\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"url\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"publishedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"impactRank\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Alert\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"indicatorName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"period\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"valueA\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"valueB\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"sourceA\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sourceB\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"diff\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"diffRatio\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ServerNode\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"cpuName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"os\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"region\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memTotal\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"diskTotal\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"cpuCores\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"cpuUsage\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"ramUsed\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"swapUsed\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"load1\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"load5\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"load15\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"diskUsed\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"netIn\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"netOut\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"uptime\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"process\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"connections\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"status\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"recordedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ServerNodeHistory\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"period\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"cpu\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"ram\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"load\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"disk\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"netIn\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"netOut\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"process\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"connections\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ServiceCheck\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"url\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"status\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"latencyMs\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"message\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastCheckAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Econ_SowInventory\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"month\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"inventory\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ScraperCache\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"scraperName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastDataHash\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastUpdateAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"metadata\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"OilPriceHistory\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"province\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"fuel\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"price\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"source\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"MihomeAccount\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"key\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"status\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastError\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"MihomeHome\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"homeId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"devices\",\"kind\":\"object\",\"type\":\"MihomeDevice\",\"relationName\":\"MihomeDeviceToMihomeHome\"}],\"dbName\":null},\"MihomeDevice\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"did\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"homeId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"home\",\"kind\":\"object\",\"type\":\"MihomeHome\",\"relationName\":\"MihomeDeviceToMihomeHome\"},{\"name\":\"model\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"alias\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"tempSiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"tempPiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"humSiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"humPiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"battSiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"battPiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"battery\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"batteryAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"enabled\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"lastSyncAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"samples\",\"kind\":\"object\",\"type\":\"MihomeEnvSample\",\"relationName\":\"MihomeDeviceToMihomeEnvSample\"},{\"name\":\"hours\",\"kind\":\"object\",\"type\":\"MihomeEnvHour\",\"relationName\":\"MihomeDeviceToMihomeEnvHour\"}],\"dbName\":null},\"MihomeEnvSample\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"deviceId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"device\",\"kind\":\"object\",\"type\":\"MihomeDevice\",\"relationName\":\"MihomeDeviceToMihomeEnvSample\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"temperature\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidity\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"temperatureAvg\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidityAvg\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"temperatureMin\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"temperatureMax\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidityMin\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidityMax\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"origin\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sampledAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"raw\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"MihomeEnvHour\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"deviceId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"device\",\"kind\":\"object\",\"type\":\"MihomeDevice\",\"relationName\":\"MihomeDeviceToMihomeEnvHour\"},{\"name\":\"hour\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"temperatureAvg\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidityAvg\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"raw\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") +config.runtimeDataModel = JSON.parse("{\"models\":{\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"habits\",\"kind\":\"object\",\"type\":\"Habit\",\"relationName\":\"HabitToUser\"},{\"name\":\"checkIns\",\"kind\":\"object\",\"type\":\"CheckIn\",\"relationName\":\"CheckInToUser\"},{\"name\":\"checkLists\",\"kind\":\"object\",\"type\":\"CheckList\",\"relationName\":\"CheckListToUser\"},{\"name\":\"templates\",\"kind\":\"object\",\"type\":\"CheckListTemplate\",\"relationName\":\"CheckListTemplateToUser\"},{\"name\":\"ownedProjects\",\"kind\":\"object\",\"type\":\"Project\",\"relationName\":\"ProjectOwner\"},{\"name\":\"projectMembers\",\"kind\":\"object\",\"type\":\"ProjectMember\",\"relationName\":\"ProjectMemberToUser\"},{\"name\":\"assignedTasks\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"TaskAssignee\"},{\"name\":\"createdTasks\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"TaskCreator\"},{\"name\":\"taskMembers\",\"kind\":\"object\",\"type\":\"TaskMember\",\"relationName\":\"TaskMemberToUser\"}],\"dbName\":null},\"Article\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"slug\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"content\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Strategy\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"data\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Indicator\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"groupName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"unit\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"source\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"frequency\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"hidden\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"points\",\"kind\":\"object\",\"type\":\"IndicatorPoint\",\"relationName\":\"IndicatorToIndicatorPoint\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"IndicatorPoint\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"indicatorId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"indicator\",\"kind\":\"object\",\"type\":\"Indicator\",\"relationName\":\"IndicatorToIndicatorPoint\"},{\"name\":\"period\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"value\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"News\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"summary\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"content\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"source\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"url\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"publishedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"impactRank\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Alert\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"indicatorName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"period\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"valueA\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"valueB\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"sourceA\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sourceB\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"diff\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"diffRatio\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ServerNode\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"cpuName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"os\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"region\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"memTotal\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"diskTotal\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"cpuCores\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"cpuUsage\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"ramUsed\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"swapUsed\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"load1\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"load5\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"load15\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"diskUsed\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"netIn\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"netOut\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"uptime\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"process\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"connections\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"status\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"recordedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ServerNodeHistory\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"period\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"cpu\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"ram\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"load\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"disk\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"netIn\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"netOut\",\"kind\":\"scalar\",\"type\":\"BigInt\"},{\"name\":\"process\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"connections\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ServiceCheck\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"url\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"status\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"latencyMs\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"message\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastCheckAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Econ_SowInventory\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"month\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"inventory\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"ScraperCache\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"scraperName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastDataHash\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastUpdateAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"metadata\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"OilPriceHistory\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"province\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"fuel\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"price\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"source\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"MihomeAccount\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"key\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"status\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastError\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"MihomeHome\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"homeId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"devices\",\"kind\":\"object\",\"type\":\"MihomeDevice\",\"relationName\":\"MihomeDeviceToMihomeHome\"}],\"dbName\":null},\"MihomeDevice\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"did\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"homeId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"home\",\"kind\":\"object\",\"type\":\"MihomeHome\",\"relationName\":\"MihomeDeviceToMihomeHome\"},{\"name\":\"model\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"alias\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"tempSiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"tempPiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"humSiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"humPiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"battSiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"battPiid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"battery\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"batteryAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"enabled\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"lastSyncAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"samples\",\"kind\":\"object\",\"type\":\"MihomeEnvSample\",\"relationName\":\"MihomeDeviceToMihomeEnvSample\"},{\"name\":\"hours\",\"kind\":\"object\",\"type\":\"MihomeEnvHour\",\"relationName\":\"MihomeDeviceToMihomeEnvHour\"}],\"dbName\":null},\"MihomeEnvSample\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"deviceId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"device\",\"kind\":\"object\",\"type\":\"MihomeDevice\",\"relationName\":\"MihomeDeviceToMihomeEnvSample\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"temperature\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidity\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"temperatureAvg\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidityAvg\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"temperatureMin\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"temperatureMax\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidityMin\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidityMax\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"origin\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sampledAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"raw\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"MihomeEnvHour\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"deviceId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"device\",\"kind\":\"object\",\"type\":\"MihomeDevice\",\"relationName\":\"MihomeDeviceToMihomeEnvHour\"},{\"name\":\"hour\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"temperatureAvg\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"humidityAvg\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"raw\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Habit\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"HabitToUser\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"period\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"target\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"color\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"icon\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"note\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"archived\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"records\",\"kind\":\"object\",\"type\":\"CheckIn\",\"relationName\":\"CheckInToHabit\"}],\"dbName\":null},\"CheckIn\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"habitId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"habit\",\"kind\":\"object\",\"type\":\"Habit\",\"relationName\":\"CheckInToHabit\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"CheckInToUser\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"seq\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"note\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"source\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"CheckList\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"CheckListToUser\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"scene\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"note\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"archived\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"items\",\"kind\":\"object\",\"type\":\"CheckListItem\",\"relationName\":\"CheckListToCheckListItem\"}],\"dbName\":null},\"CheckListItem\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"listId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"list\",\"kind\":\"object\",\"type\":\"CheckList\",\"relationName\":\"CheckListToCheckListItem\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"note\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"done\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"doneAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"CheckListTemplate\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"CheckListTemplateToUser\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"scene\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"items\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"builtin\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Project\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"code\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ownerId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"owner\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ProjectOwner\"},{\"name\":\"isPersonal\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"archived\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"notes\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"taskKinds\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"icon\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"autoArchive\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"members\",\"kind\":\"object\",\"type\":\"ProjectMember\",\"relationName\":\"ProjectToProjectMember\"},{\"name\":\"columns\",\"kind\":\"object\",\"type\":\"BoardColumn\",\"relationName\":\"BoardColumnToProject\"},{\"name\":\"tasks\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"ProjectToTask\"}],\"dbName\":null},\"ProjectMember\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"project\",\"kind\":\"object\",\"type\":\"Project\",\"relationName\":\"ProjectToProjectMember\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"ProjectMemberToUser\"},{\"name\":\"permission\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"BoardColumn\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"project\",\"kind\":\"object\",\"type\":\"Project\",\"relationName\":\"BoardColumnToProject\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"boundStatus\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"retention\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"tasks\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"BoardColumnToTask\"}],\"dbName\":null},\"Task\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"columnUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"parentUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"project\",\"kind\":\"object\",\"type\":\"Project\",\"relationName\":\"ProjectToTask\"},{\"name\":\"columnId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"column\",\"kind\":\"object\",\"type\":\"BoardColumn\",\"relationName\":\"BoardColumnToTask\"},{\"name\":\"parentId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"parent\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"Subtask\"},{\"name\":\"children\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"Subtask\"},{\"name\":\"title\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"kind\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"markDate\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"status\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"doneAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"assigneeId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"assignee\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"TaskAssignee\"},{\"name\":\"creatorId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"creator\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"TaskCreator\"},{\"name\":\"extras\",\"kind\":\"scalar\",\"type\":\"Json\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"archived\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"archivedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"archiveSource\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"members\",\"kind\":\"object\",\"type\":\"TaskMember\",\"relationName\":\"TaskToTaskMember\"},{\"name\":\"tags\",\"kind\":\"object\",\"type\":\"TaskTag\",\"relationName\":\"TaskToTaskTag\"},{\"name\":\"number\",\"kind\":\"scalar\",\"type\":\"Int\"}],\"dbName\":null},\"TaskMember\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"taskUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"taskId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"task\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"TaskToTaskMember\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"user\",\"kind\":\"object\",\"type\":\"User\",\"relationName\":\"TaskMemberToUser\"}],\"dbName\":null},\"TaskTag\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"uuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"taskUuid\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"taskId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"task\",\"kind\":\"object\",\"type\":\"Task\",\"relationName\":\"TaskToTaskTag\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") config.parameterizationSchema = { - strings: JSON.parse("[\"where\",\"User.findUnique\",\"User.findUniqueOrThrow\",\"orderBy\",\"cursor\",\"User.findFirst\",\"User.findFirstOrThrow\",\"User.findMany\",\"data\",\"User.createOne\",\"User.createMany\",\"User.updateOne\",\"User.updateMany\",\"create\",\"update\",\"User.upsertOne\",\"User.deleteOne\",\"User.deleteMany\",\"having\",\"_count\",\"_avg\",\"_sum\",\"_min\",\"_max\",\"User.groupBy\",\"User.aggregate\",\"Article.findUnique\",\"Article.findUniqueOrThrow\",\"Article.findFirst\",\"Article.findFirstOrThrow\",\"Article.findMany\",\"Article.createOne\",\"Article.createMany\",\"Article.updateOne\",\"Article.updateMany\",\"Article.upsertOne\",\"Article.deleteOne\",\"Article.deleteMany\",\"Article.groupBy\",\"Article.aggregate\",\"Strategy.findUnique\",\"Strategy.findUniqueOrThrow\",\"Strategy.findFirst\",\"Strategy.findFirstOrThrow\",\"Strategy.findMany\",\"Strategy.createOne\",\"Strategy.createMany\",\"Strategy.updateOne\",\"Strategy.updateMany\",\"Strategy.upsertOne\",\"Strategy.deleteOne\",\"Strategy.deleteMany\",\"Strategy.groupBy\",\"Strategy.aggregate\",\"indicator\",\"points\",\"Indicator.findUnique\",\"Indicator.findUniqueOrThrow\",\"Indicator.findFirst\",\"Indicator.findFirstOrThrow\",\"Indicator.findMany\",\"Indicator.createOne\",\"Indicator.createMany\",\"Indicator.updateOne\",\"Indicator.updateMany\",\"Indicator.upsertOne\",\"Indicator.deleteOne\",\"Indicator.deleteMany\",\"Indicator.groupBy\",\"Indicator.aggregate\",\"IndicatorPoint.findUnique\",\"IndicatorPoint.findUniqueOrThrow\",\"IndicatorPoint.findFirst\",\"IndicatorPoint.findFirstOrThrow\",\"IndicatorPoint.findMany\",\"IndicatorPoint.createOne\",\"IndicatorPoint.createMany\",\"IndicatorPoint.updateOne\",\"IndicatorPoint.updateMany\",\"IndicatorPoint.upsertOne\",\"IndicatorPoint.deleteOne\",\"IndicatorPoint.deleteMany\",\"IndicatorPoint.groupBy\",\"IndicatorPoint.aggregate\",\"News.findUnique\",\"News.findUniqueOrThrow\",\"News.findFirst\",\"News.findFirstOrThrow\",\"News.findMany\",\"News.createOne\",\"News.createMany\",\"News.updateOne\",\"News.updateMany\",\"News.upsertOne\",\"News.deleteOne\",\"News.deleteMany\",\"News.groupBy\",\"News.aggregate\",\"Alert.findUnique\",\"Alert.findUniqueOrThrow\",\"Alert.findFirst\",\"Alert.findFirstOrThrow\",\"Alert.findMany\",\"Alert.createOne\",\"Alert.createMany\",\"Alert.updateOne\",\"Alert.updateMany\",\"Alert.upsertOne\",\"Alert.deleteOne\",\"Alert.deleteMany\",\"Alert.groupBy\",\"Alert.aggregate\",\"ServerNode.findUnique\",\"ServerNode.findUniqueOrThrow\",\"ServerNode.findFirst\",\"ServerNode.findFirstOrThrow\",\"ServerNode.findMany\",\"ServerNode.createOne\",\"ServerNode.createMany\",\"ServerNode.updateOne\",\"ServerNode.updateMany\",\"ServerNode.upsertOne\",\"ServerNode.deleteOne\",\"ServerNode.deleteMany\",\"ServerNode.groupBy\",\"ServerNode.aggregate\",\"ServerNodeHistory.findUnique\",\"ServerNodeHistory.findUniqueOrThrow\",\"ServerNodeHistory.findFirst\",\"ServerNodeHistory.findFirstOrThrow\",\"ServerNodeHistory.findMany\",\"ServerNodeHistory.createOne\",\"ServerNodeHistory.createMany\",\"ServerNodeHistory.updateOne\",\"ServerNodeHistory.updateMany\",\"ServerNodeHistory.upsertOne\",\"ServerNodeHistory.deleteOne\",\"ServerNodeHistory.deleteMany\",\"ServerNodeHistory.groupBy\",\"ServerNodeHistory.aggregate\",\"ServiceCheck.findUnique\",\"ServiceCheck.findUniqueOrThrow\",\"ServiceCheck.findFirst\",\"ServiceCheck.findFirstOrThrow\",\"ServiceCheck.findMany\",\"ServiceCheck.createOne\",\"ServiceCheck.createMany\",\"ServiceCheck.updateOne\",\"ServiceCheck.updateMany\",\"ServiceCheck.upsertOne\",\"ServiceCheck.deleteOne\",\"ServiceCheck.deleteMany\",\"ServiceCheck.groupBy\",\"ServiceCheck.aggregate\",\"Econ_SowInventory.findUnique\",\"Econ_SowInventory.findUniqueOrThrow\",\"Econ_SowInventory.findFirst\",\"Econ_SowInventory.findFirstOrThrow\",\"Econ_SowInventory.findMany\",\"Econ_SowInventory.createOne\",\"Econ_SowInventory.createMany\",\"Econ_SowInventory.updateOne\",\"Econ_SowInventory.updateMany\",\"Econ_SowInventory.upsertOne\",\"Econ_SowInventory.deleteOne\",\"Econ_SowInventory.deleteMany\",\"Econ_SowInventory.groupBy\",\"Econ_SowInventory.aggregate\",\"ScraperCache.findUnique\",\"ScraperCache.findUniqueOrThrow\",\"ScraperCache.findFirst\",\"ScraperCache.findFirstOrThrow\",\"ScraperCache.findMany\",\"ScraperCache.createOne\",\"ScraperCache.createMany\",\"ScraperCache.updateOne\",\"ScraperCache.updateMany\",\"ScraperCache.upsertOne\",\"ScraperCache.deleteOne\",\"ScraperCache.deleteMany\",\"ScraperCache.groupBy\",\"ScraperCache.aggregate\",\"OilPriceHistory.findUnique\",\"OilPriceHistory.findUniqueOrThrow\",\"OilPriceHistory.findFirst\",\"OilPriceHistory.findFirstOrThrow\",\"OilPriceHistory.findMany\",\"OilPriceHistory.createOne\",\"OilPriceHistory.createMany\",\"OilPriceHistory.updateOne\",\"OilPriceHistory.updateMany\",\"OilPriceHistory.upsertOne\",\"OilPriceHistory.deleteOne\",\"OilPriceHistory.deleteMany\",\"OilPriceHistory.groupBy\",\"OilPriceHistory.aggregate\",\"MihomeAccount.findUnique\",\"MihomeAccount.findUniqueOrThrow\",\"MihomeAccount.findFirst\",\"MihomeAccount.findFirstOrThrow\",\"MihomeAccount.findMany\",\"MihomeAccount.createOne\",\"MihomeAccount.createMany\",\"MihomeAccount.updateOne\",\"MihomeAccount.updateMany\",\"MihomeAccount.upsertOne\",\"MihomeAccount.deleteOne\",\"MihomeAccount.deleteMany\",\"MihomeAccount.groupBy\",\"MihomeAccount.aggregate\",\"home\",\"device\",\"samples\",\"hours\",\"devices\",\"MihomeHome.findUnique\",\"MihomeHome.findUniqueOrThrow\",\"MihomeHome.findFirst\",\"MihomeHome.findFirstOrThrow\",\"MihomeHome.findMany\",\"MihomeHome.createOne\",\"MihomeHome.createMany\",\"MihomeHome.updateOne\",\"MihomeHome.updateMany\",\"MihomeHome.upsertOne\",\"MihomeHome.deleteOne\",\"MihomeHome.deleteMany\",\"MihomeHome.groupBy\",\"MihomeHome.aggregate\",\"MihomeDevice.findUnique\",\"MihomeDevice.findUniqueOrThrow\",\"MihomeDevice.findFirst\",\"MihomeDevice.findFirstOrThrow\",\"MihomeDevice.findMany\",\"MihomeDevice.createOne\",\"MihomeDevice.createMany\",\"MihomeDevice.updateOne\",\"MihomeDevice.updateMany\",\"MihomeDevice.upsertOne\",\"MihomeDevice.deleteOne\",\"MihomeDevice.deleteMany\",\"MihomeDevice.groupBy\",\"MihomeDevice.aggregate\",\"MihomeEnvSample.findUnique\",\"MihomeEnvSample.findUniqueOrThrow\",\"MihomeEnvSample.findFirst\",\"MihomeEnvSample.findFirstOrThrow\",\"MihomeEnvSample.findMany\",\"MihomeEnvSample.createOne\",\"MihomeEnvSample.createMany\",\"MihomeEnvSample.updateOne\",\"MihomeEnvSample.updateMany\",\"MihomeEnvSample.upsertOne\",\"MihomeEnvSample.deleteOne\",\"MihomeEnvSample.deleteMany\",\"MihomeEnvSample.groupBy\",\"MihomeEnvSample.aggregate\",\"MihomeEnvHour.findUnique\",\"MihomeEnvHour.findUniqueOrThrow\",\"MihomeEnvHour.findFirst\",\"MihomeEnvHour.findFirstOrThrow\",\"MihomeEnvHour.findMany\",\"MihomeEnvHour.createOne\",\"MihomeEnvHour.createMany\",\"MihomeEnvHour.updateOne\",\"MihomeEnvHour.updateMany\",\"MihomeEnvHour.upsertOne\",\"MihomeEnvHour.deleteOne\",\"MihomeEnvHour.deleteMany\",\"MihomeEnvHour.groupBy\",\"MihomeEnvHour.aggregate\",\"AND\",\"OR\",\"NOT\",\"id\",\"deviceId\",\"hour\",\"temperatureAvg\",\"humidityAvg\",\"raw\",\"createdAt\",\"equals\",\"string_contains\",\"string_starts_with\",\"string_ends_with\",\"array_starts_with\",\"array_ends_with\",\"array_contains\",\"lt\",\"lte\",\"gt\",\"gte\",\"not\",\"in\",\"notIn\",\"date\",\"temperature\",\"humidity\",\"temperatureMin\",\"temperatureMax\",\"humidityMin\",\"humidityMax\",\"origin\",\"sampledAt\",\"contains\",\"startsWith\",\"endsWith\",\"search\",\"did\",\"homeId\",\"model\",\"name\",\"alias\",\"tempSiid\",\"tempPiid\",\"humSiid\",\"humPiid\",\"battSiid\",\"battPiid\",\"battery\",\"batteryAt\",\"enabled\",\"lastSyncAt\",\"every\",\"some\",\"none\",\"deviceId_hour\",\"deviceId_date\",\"key\",\"userId\",\"status\",\"lastError\",\"updatedAt\",\"province\",\"fuel\",\"price\",\"source\",\"date_province_fuel\",\"scraperName\",\"lastDataHash\",\"lastUpdateAt\",\"metadata\",\"month\",\"inventory\",\"url\",\"latencyMs\",\"message\",\"lastCheckAt\",\"uuid\",\"period\",\"cpu\",\"ram\",\"load\",\"disk\",\"netIn\",\"netOut\",\"process\",\"connections\",\"uuid_period\",\"cpuName\",\"os\",\"region\",\"memTotal\",\"diskTotal\",\"cpuCores\",\"cpuUsage\",\"ramUsed\",\"swapUsed\",\"load1\",\"load5\",\"load15\",\"diskUsed\",\"uptime\",\"recordedAt\",\"indicatorName\",\"valueA\",\"valueB\",\"sourceA\",\"sourceB\",\"diff\",\"diffRatio\",\"title\",\"summary\",\"content\",\"publishedAt\",\"impactRank\",\"title_publishedAt\",\"indicatorId\",\"value\",\"groupName\",\"unit\",\"frequency\",\"hidden\",\"indicatorId_period\",\"description\",\"slug\",\"email\",\"password\",\"is\",\"isNot\",\"connectOrCreate\",\"upsert\",\"createMany\",\"set\",\"disconnect\",\"delete\",\"connect\",\"updateMany\",\"deleteMany\",\"_relevance\",\"increment\",\"decrement\",\"multiply\",\"divide\"]"), - graph: "2gWUAfwBCY8CAACFBAAwkAIAAAQAEJECAACFBAAwkgICAAAAAZgCQADJAwAhtwIBAMgDACHMAkAAyQMAIYwDAQAAAAGNAwEAyAMAIQEAAAABACABAAAAAQAgCY8CAACFBAAwkAIAAAQAEJECAACFBAAwkgICAMcDACGYAkAAyQMAIbcCAQDIAwAhzAJAAMkDACGMAwEAyAMAIY0DAQDIAwAhAZkDAADCBQAgAwAAAAQAIAMAAAUAMAQAAAEAIAMAAAAEACADAAAFADAEAAABACADAAAABAAgAwAABQAwBAAAAQAgBpICAgAAAAGYAkAAAAABtwIBAAAAAcwCQAAAAAGMAwEAAAABjQMBAAAAAQEIAAAJACAGkgICAAAAAZgCQAAAAAG3AgEAAAABzAJAAAAAAYwDAQAAAAGNAwEAAAABAQgAAAsAMAaSAgIAjgQAIZgCQACMBAAhtwIBAJYEACHMAkAAjAQAIYwDAQCWBAAhjQMBAJYEACECAAAAAQAgCAAADQAgBpICAgCOBAAhmAJAAIwEACG3AgEAlgQAIcwCQACMBAAhjAMBAJYEACGNAwEAlgQAIQIAAAAEACAIAAAPACADAAAAAQAgDQAACQAgDgAADQAgAQAAAAEAIAEAAAAEACAFEwAAvQUAIBQAAL4FACAVAADBBQAgFgAAwAUAIBcAAL8FACAJjwIAAIQEADCQAgAAFQAQkQIAAIQEADCSAgIAqAMAIZgCQACpAwAhtwIBALYDACHMAkAAqQMAIYwDAQC2AwAhjQMBALYDACEDAAAABAAgAwAAFAAwEgAAFQAgAwAAAAQAIAMAAAUAMAQAAAEAIAqPAgAAgwQAMJACAAAbABCRAgAAgwQAMJICAgAAAAGYAkAAyQMAIacCAQDIAwAhzAJAAMkDACH9AgEAyAMAIf8CAQDIAwAhiwMBAAAAAQEAAAAYACABAAAAGAAgCo8CAACDBAAwkAIAABsAEJECAACDBAAwkgICAMcDACGYAkAAyQMAIacCAQDIAwAhzAJAAMkDACH9AgEAyAMAIf8CAQDIAwAhiwMBAMgDACEBmQMAALwFACADAAAAGwAgAwAAHAAwBAAAGAAgAwAAABsAIAMAABwAMAQAABgAIAMAAAAbACADAAAcADAEAAAYACAHkgICAAAAAZgCQAAAAAGnAgEAAAABzAJAAAAAAf0CAQAAAAH_AgEAAAABiwMBAAAAAQEIAAAgACAHkgICAAAAAZgCQAAAAAGnAgEAAAABzAJAAAAAAf0CAQAAAAH_AgEAAAABiwMBAAAAAQEIAAAiADAHkgICAI4EACGYAkAAjAQAIacCAQCWBAAhzAJAAIwEACH9AgEAlgQAIf8CAQCWBAAhiwMBAJYEACECAAAAGAAgCAAAJAAgB5ICAgCOBAAhmAJAAIwEACGnAgEAlgQAIcwCQACMBAAh_QIBAJYEACH_AgEAlgQAIYsDAQCWBAAhAgAAABsAIAgAACYAIAMAAAAYACANAAAgACAOAAAkACABAAAAGAAgAQAAABsAIAUTAAC3BQAgFAAAuAUAIBUAALsFACAWAAC6BQAgFwAAuQUAIAqPAgAAggQAMJACAAAsABCRAgAAggQAMJICAgCoAwAhmAJAAKkDACGnAgEAtgMAIcwCQACpAwAh_QIBALYDACH_AgEAtgMAIYsDAQC2AwAhAwAAABsAIAMAACsAMBIAACwAIAMAAAAbACADAAAcADAEAAAYACAJCAAAgQQAII8CAACABAAwkAIAADIAEJECAACABAAwkgICAAAAAZgCQADJAwAhtwIBAMgDACHMAkAAyQMAIYoDAQDIAwAhAQAAAC8AIAEAAAAvACAJCAAAgQQAII8CAACABAAwkAIAADIAEJECAACABAAwkgICAMcDACGYAkAAyQMAIbcCAQDIAwAhzAJAAMkDACGKAwEAyAMAIQGZAwAAtgUAIAMAAAAyACADAAAzADAEAAAvACADAAAAMgAgAwAAMwAwBAAALwAgAwAAADIAIAMAADMAMAQAAC8AIAYIgAAAAAGSAgIAAAABmAJAAAAAAbcCAQAAAAHMAkAAAAABigMBAAAAAQEIAAA3ACAGCIAAAAABkgICAAAAAZgCQAAAAAG3AgEAAAABzAJAAAAAAYoDAQAAAAEBCAAAOQAwBgiAAAAAAZICAgCOBAAhmAJAAIwEACG3AgEAlgQAIcwCQACMBAAhigMBAJYEACECAAAALwAgCAAAOwAgBgiAAAAAAZICAgCOBAAhmAJAAIwEACG3AgEAlgQAIcwCQACMBAAhigMBAJYEACECAAAAMgAgCAAAPQAgAwAAAC8AIA0AADcAIA4AADsAIAEAAAAvACABAAAAMgAgBRMAALEFACAUAACyBQAgFQAAtQUAIBYAALQFACAXAACzBQAgCQgAAP4DACCPAgAA_QMAMJACAABDABCRAgAA_QMAMJICAgCoAwAhmAJAAKkDACG3AgEAtgMAIcwCQACpAwAhigMBALYDACEDAAAAMgAgAwAAQgAwEgAAQwAgAwAAADIAIAMAADMAMAQAAC8AIA43AAD5AwAgjwIAAPgDADCQAgAATgAQkQIAAPgDADCSAgIAAAABmAJAAMkDACG3AgEAAAABzAJAAMkDACHQAgEAyAMAIf0CAQDIAwAhhQMBAMgDACGGAwEAyAMAIYcDAQDIAwAhiAMgANYDACEBAAAARgAgCjYAAPwDACCPAgAA-wMAMJACAABIABCRAgAA-wMAMJICAgDHAwAhmAJAAMkDACHMAkAAyQMAId0CAQDIAwAhgwMCAMcDACGEAwgA4AMAIQI2AACvBQAgmQMAALAFACALNgAA_AMAII8CAAD7AwAwkAIAAEgAEJECAAD7AwAwkgICAAAAAZgCQADJAwAhzAJAAMkDACHdAgEAyAMAIYMDAgDHAwAhhAMIAOADACGJAwAA-gMAIAMAAABIACADAABJADAEAABKACABAAAASAAgAQAAAEYAIA43AAD5AwAgjwIAAPgDADCQAgAATgAQkQIAAPgDADCSAgIAxwMAIZgCQADJAwAhtwIBAMgDACHMAkAAyQMAIdACAQDIAwAh_QIBAMgDACGFAwEAyAMAIYYDAQDIAwAhhwMBAMgDACGIAyAA1gMAIQI3AACtBQAgmQMAAK4FACADAAAATgAgAwAATwAwBAAARgAgAwAAAE4AIAMAAE8AMAQAAEYAIAMAAABOACADAABPADAEAABGACALNwAArAUAIJICAgAAAAGYAkAAAAABtwIBAAAAAcwCQAAAAAHQAgEAAAAB_QIBAAAAAYUDAQAAAAGGAwEAAAABhwMBAAAAAYgDIAAAAAEBCAAAUwAgCpICAgAAAAGYAkAAAAABtwIBAAAAAcwCQAAAAAHQAgEAAAAB_QIBAAAAAYUDAQAAAAGGAwEAAAABhwMBAAAAAYgDIAAAAAEBCAAAVQAwCzcAAJ8FACCSAgIAjgQAIZgCQACMBAAhtwIBAJYEACHMAkAAjAQAIdACAQCWBAAh_QIBAJYEACGFAwEAlgQAIYYDAQCWBAAhhwMBAJYEACGIAyAAoQQAIQIAAABGACAIAABXACAKkgICAI4EACGYAkAAjAQAIbcCAQCWBAAhzAJAAIwEACHQAgEAlgQAIf0CAQCWBAAhhQMBAJYEACGGAwEAlgQAIYcDAQCWBAAhiAMgAKEEACECAAAATgAgCAAAWQAgAwAAAEYAIA0AAFMAIA4AAFcAIAEAAABGACABAAAATgAgBRMAAJoFACAUAACbBQAgFQAAngUAIBYAAJ0FACAXAACcBQAgDY8CAAD3AwAwkAIAAF8AEJECAAD3AwAwkgICAKgDACGYAkAAqQMAIbcCAQC2AwAhzAJAAKkDACHQAgEAtgMAIf0CAQC2AwAhhQMBALYDACGGAwEAtgMAIYcDAQC2AwAhiAMgAL8DACEDAAAATgAgAwAAXgAwEgAAXwAgAwAAAE4AIAMAAE8AMAQAAEYAIAEAAABKACABAAAASgAgAwAAAEgAIAMAAEkAMAQAAEoAIAMAAABIACADAABJADAEAABKACADAAAASAAgAwAASQAwBAAASgAgBzYAAJkFACCSAgIAAAABmAJAAAAAAcwCQAAAAAHdAgEAAAABgwMCAAAAAYQDCAAAAAEBCAAAZwAgBpICAgAAAAGYAkAAAAABzAJAAAAAAd0CAQAAAAGDAwIAAAABhAMIAAAAAQEIAABpADAHNgAAmAUAIJICAgCOBAAhmAJAAIwEACHMAkAAjAQAId0CAQCWBAAhgwMCAI4EACGEAwgA5gQAIQIAAABKACAIAABrACAGkgICAI4EACGYAkAAjAQAIcwCQACMBAAh3QIBAJYEACGDAwIAjgQAIYQDCADmBAAhAgAAAEgAIAgAAG0AIAMAAABKACANAABnACAOAABrACABAAAASgAgAQAAAEgAIAUTAACTBQAgFAAAlAUAIBUAAJcFACAWAACWBQAgFwAAlQUAIAmPAgAA9gMAMJACAABzABCRAgAA9gMAMJICAgCoAwAhmAJAAKkDACHMAkAAqQMAId0CAQC2AwAhgwMCAKgDACGEAwgA3QMAIQMAAABIACADAAByADASAABzACADAAAASAAgAwAASQAwBAAASgAgDY8CAAD0AwAwkAIAAHkAEJECAAD0AwAwkgICAAAAAZgCQADJAwAh0AIBAMgDACHYAgEAyAMAIf0CAQDIAwAh_gIBAMgDACH_AgEA1AMAIYADQADJAwAhgQMCAMcDACGCAwAA9QMAIAEAAAB2ACABAAAAdgAgDI8CAAD0AwAwkAIAAHkAEJECAAD0AwAwkgICAMcDACGYAkAAyQMAIdACAQDIAwAh2AIBAMgDACH9AgEAyAMAIf4CAQDIAwAh_wIBANQDACGAA0AAyQMAIYEDAgDHAwAhAv8CAACGBAAgmQMAAJIFACADAAAAeQAgAwAAegAwBAAAdgAgAwAAAHkAIAMAAHoAMAQAAHYAIAMAAAB5ACADAAB6ADAEAAB2ACAJkgICAAAAAZgCQAAAAAHQAgEAAAAB2AIBAAAAAf0CAQAAAAH-AgEAAAAB_wIBAAAAAYADQAAAAAGBAwIAAAABAQgAAH4AIAmSAgIAAAABmAJAAAAAAdACAQAAAAHYAgEAAAAB_QIBAAAAAf4CAQAAAAH_AgEAAAABgANAAAAAAYEDAgAAAAEBCAAAgAEAMAmSAgIAjgQAIZgCQACMBAAh0AIBAJYEACHYAgEAlgQAIf0CAQCWBAAh_gIBAJYEACH_AgEAnwQAIYADQACMBAAhgQMCAI4EACECAAAAdgAgCAAAggEAIAmSAgIAjgQAIZgCQACMBAAh0AIBAJYEACHYAgEAlgQAIf0CAQCWBAAh_gIBAJYEACH_AgEAnwQAIYADQACMBAAhgQMCAI4EACECAAAAeQAgCAAAhAEAIAMAAAB2ACANAAB-ACAOAACCAQAgAQAAAHYAIAEAAAB5ACAGEwAAjQUAIBQAAI4FACAVAACRBQAgFgAAkAUAIBcAAI8FACD_AgAAhgQAIAyPAgAA8wMAMJACAACKAQAQkQIAAPMDADCSAgIAqAMAIZgCQACpAwAh0AIBALYDACHYAgEAtgMAIf0CAQC2AwAh_gIBALYDACH_AgEAvQMAIYADQACpAwAhgQMCAKgDACEDAAAAeQAgAwAAiQEAMBIAAIoBACADAAAAeQAgAwAAegAwBAAAdgAgDY8CAADyAwAwkAIAAJABABCRAgAA8gMAMJICAgAAAAGYAkAAyQMAId0CAQDIAwAh9gIBAMgDACH3AggA4AMAIfgCCADgAwAh-QIBAMgDACH6AgEAyAMAIfsCCADgAwAh_AIIAOADACEBAAAAjQEAIAEAAACNAQAgDY8CAADyAwAwkAIAAJABABCRAgAA8gMAMJICAgDHAwAhmAJAAMkDACHdAgEAyAMAIfYCAQDIAwAh9wIIAOADACH4AggA4AMAIfkCAQDIAwAh-gIBAMgDACH7AggA4AMAIfwCCADgAwAhAZkDAACMBQAgAwAAAJABACADAACRAQAwBAAAjQEAIAMAAACQAQAgAwAAkQEAMAQAAI0BACADAAAAkAEAIAMAAJEBADAEAACNAQAgCpICAgAAAAGYAkAAAAAB3QIBAAAAAfYCAQAAAAH3AggAAAAB-AIIAAAAAfkCAQAAAAH6AgEAAAAB-wIIAAAAAfwCCAAAAAEBCAAAlQEAIAqSAgIAAAABmAJAAAAAAd0CAQAAAAH2AgEAAAAB9wIIAAAAAfgCCAAAAAH5AgEAAAAB-gIBAAAAAfsCCAAAAAH8AggAAAABAQgAAJcBADAKkgICAI4EACGYAkAAjAQAId0CAQCWBAAh9gIBAJYEACH3AggA5gQAIfgCCADmBAAh-QIBAJYEACH6AgEAlgQAIfsCCADmBAAh_AIIAOYEACECAAAAjQEAIAgAAJkBACAKkgICAI4EACGYAkAAjAQAId0CAQCWBAAh9gIBAJYEACH3AggA5gQAIfgCCADmBAAh-QIBAJYEACH6AgEAlgQAIfsCCADmBAAh_AIIAOYEACECAAAAkAEAIAgAAJsBACADAAAAjQEAIA0AAJUBACAOAACZAQAgAQAAAI0BACABAAAAkAEAIAUTAACHBQAgFAAAiAUAIBUAAIsFACAWAACKBQAgFwAAiQUAIA2PAgAA8QMAMJACAAChAQAQkQIAAPEDADCSAgIAqAMAIZgCQACpAwAh3QIBALYDACH2AgEAtgMAIfcCCADdAwAh-AIIAN0DACH5AgEAtgMAIfoCAQC2AwAh-wIIAN0DACH8AggA3QMAIQMAAACQAQAgAwAAoAEAMBIAAKEBACADAAAAkAEAIAMAAJEBADAEAACNAQAgHI8CAADwAwAwkAIAAKcBABCRAgAA8AMAMJICAgAAAAGYAkAAyQMAIbcCAQDIAwAhygIBAMgDACHMAkAAyQMAIdwCAQAAAAHiAgQA7QMAIeMCBADtAwAh5AICAMcDACHlAgIAxwMAIecCAQDIAwAh6AIBAMgDACHpAgEAyAMAIeoCBADtAwAh6wIEAO0DACHsAgIAxwMAIe0CCADgAwAh7gIEAO0DACHvAgQA7QMAIfACCADgAwAh8QIIAOADACHyAggA4AMAIfMCBADtAwAh9AIEAO0DACH1AkAAyQMAIQEAAACkAQAgAQAAAKQBACAcjwIAAPADADCQAgAApwEAEJECAADwAwAwkgICAMcDACGYAkAAyQMAIbcCAQDIAwAhygIBAMgDACHMAkAAyQMAIdwCAQDIAwAh4gIEAO0DACHjAgQA7QMAIeQCAgDHAwAh5QICAMcDACHnAgEAyAMAIegCAQDIAwAh6QIBAMgDACHqAgQA7QMAIesCBADtAwAh7AICAMcDACHtAggA4AMAIe4CBADtAwAh7wIEAO0DACHwAggA4AMAIfECCADgAwAh8gIIAOADACHzAgQA7QMAIfQCBADtAwAh9QJAAMkDACEBmQMAAIYFACADAAAApwEAIAMAAKgBADAEAACkAQAgAwAAAKcBACADAACoAQAwBAAApAEAIAMAAACnAQAgAwAAqAEAMAQAAKQBACAZkgICAAAAAZgCQAAAAAG3AgEAAAABygIBAAAAAcwCQAAAAAHcAgEAAAAB4gIEAAAAAeMCBAAAAAHkAgIAAAAB5QICAAAAAecCAQAAAAHoAgEAAAAB6QIBAAAAAeoCBAAAAAHrAgQAAAAB7AICAAAAAe0CCAAAAAHuAgQAAAAB7wIEAAAAAfACCAAAAAHxAggAAAAB8gIIAAAAAfMCBAAAAAH0AgQAAAAB9QJAAAAAAQEIAACsAQAgGZICAgAAAAGYAkAAAAABtwIBAAAAAcoCAQAAAAHMAkAAAAAB3AIBAAAAAeICBAAAAAHjAgQAAAAB5AICAAAAAeUCAgAAAAHnAgEAAAAB6AIBAAAAAekCAQAAAAHqAgQAAAAB6wIEAAAAAewCAgAAAAHtAggAAAAB7gIEAAAAAe8CBAAAAAHwAggAAAAB8QIIAAAAAfICCAAAAAHzAgQAAAAB9AIEAAAAAfUCQAAAAAEBCAAArgEAMBmSAgIAjgQAIZgCQACMBAAhtwIBAJYEACHKAgEAlgQAIcwCQACMBAAh3AIBAJYEACHiAgQA_wQAIeMCBAD_BAAh5AICAI4EACHlAgIAjgQAIecCAQCWBAAh6AIBAJYEACHpAgEAlgQAIeoCBAD_BAAh6wIEAP8EACHsAgIAjgQAIe0CCADmBAAh7gIEAP8EACHvAgQA_wQAIfACCADmBAAh8QIIAOYEACHyAggA5gQAIfMCBAD_BAAh9AIEAP8EACH1AkAAjAQAIQIAAACkAQAgCAAAsAEAIBmSAgIAjgQAIZgCQACMBAAhtwIBAJYEACHKAgEAlgQAIcwCQACMBAAh3AIBAJYEACHiAgQA_wQAIeMCBAD_BAAh5AICAI4EACHlAgIAjgQAIecCAQCWBAAh6AIBAJYEACHpAgEAlgQAIeoCBAD_BAAh6wIEAP8EACHsAgIAjgQAIe0CCADmBAAh7gIEAP8EACHvAgQA_wQAIfACCADmBAAh8QIIAOYEACHyAggA5gQAIfMCBAD_BAAh9AIEAP8EACH1AkAAjAQAIQIAAACnAQAgCAAAsgEAIAMAAACkAQAgDQAArAEAIA4AALABACABAAAApAEAIAEAAACnAQAgBRMAAIEFACAUAACCBQAgFQAAhQUAIBYAAIQFACAXAACDBQAgHI8CAADvAwAwkAIAALgBABCRAgAA7wMAMJICAgCoAwAhmAJAAKkDACG3AgEAtgMAIcoCAQC2AwAhzAJAAKkDACHcAgEAtgMAIeICBADpAwAh4wIEAOkDACHkAgIAqAMAIeUCAgCoAwAh5wIBALYDACHoAgEAtgMAIekCAQC2AwAh6gIEAOkDACHrAgQA6QMAIewCAgCoAwAh7QIIAN0DACHuAgQA6QMAIe8CBADpAwAh8AIIAN0DACHxAggA3QMAIfICCADdAwAh8wIEAOkDACH0AgQA6QMAIfUCQACpAwAhAwAAAKcBACADAAC3AQAwEgAAuAEAIAMAAACnAQAgAwAAqAEAMAQAAKQBACAQjwIAAOwDADCQAgAAvgEAEJECAADsAwAwkgICAAAAAZgCQADJAwAh3AIBAMgDACHdAkAAyQMAId4CCADgAwAh3wIEAO0DACHgAggA4AMAIeECBADtAwAh4gIEAO0DACHjAgQA7QMAIeQCAgDHAwAh5QICAMcDACHmAgAA7gMAIAEAAAC7AQAgAQAAALsBACAPjwIAAOwDADCQAgAAvgEAEJECAADsAwAwkgICAMcDACGYAkAAyQMAIdwCAQDIAwAh3QJAAMkDACHeAggA4AMAId8CBADtAwAh4AIIAOADACHhAgQA7QMAIeICBADtAwAh4wIEAO0DACHkAgIAxwMAIeUCAgDHAwAhAZkDAACABQAgAwAAAL4BACADAAC_AQAwBAAAuwEAIAMAAAC-AQAgAwAAvwEAMAQAALsBACADAAAAvgEAIAMAAL8BADAEAAC7AQAgDJICAgAAAAGYAkAAAAAB3AIBAAAAAd0CQAAAAAHeAggAAAAB3wIEAAAAAeACCAAAAAHhAgQAAAAB4gIEAAAAAeMCBAAAAAHkAgIAAAAB5QICAAAAAQEIAADDAQAgDJICAgAAAAGYAkAAAAAB3AIBAAAAAd0CQAAAAAHeAggAAAAB3wIEAAAAAeACCAAAAAHhAgQAAAAB4gIEAAAAAeMCBAAAAAHkAgIAAAAB5QICAAAAAQEIAADFAQAwDJICAgCOBAAhmAJAAIwEACHcAgEAlgQAId0CQACMBAAh3gIIAOYEACHfAgQA_wQAIeACCADmBAAh4QIEAP8EACHiAgQA_wQAIeMCBAD_BAAh5AICAI4EACHlAgIAjgQAIQIAAAC7AQAgCAAAxwEAIAySAgIAjgQAIZgCQACMBAAh3AIBAJYEACHdAkAAjAQAId4CCADmBAAh3wIEAP8EACHgAggA5gQAIeECBAD_BAAh4gIEAP8EACHjAgQA_wQAIeQCAgCOBAAh5QICAI4EACECAAAAvgEAIAgAAMkBACADAAAAuwEAIA0AAMMBACAOAADHAQAgAQAAALsBACABAAAAvgEAIAUTAAD6BAAgFAAA-wQAIBUAAP4EACAWAAD9BAAgFwAA_AQAIA-PAgAA6AMAMJACAADPAQAQkQIAAOgDADCSAgIAqAMAIZgCQACpAwAh3AIBALYDACHdAkAAqQMAId4CCADdAwAh3wIEAOkDACHgAggA3QMAIeECBADpAwAh4gIEAOkDACHjAgQA6QMAIeQCAgCoAwAh5QICAKgDACEDAAAAvgEAIAMAAM4BADASAADPAQAgAwAAAL4BACADAAC_AQAwBAAAuwEAIAyPAgAA5wMAMJACAADVAQAQkQIAAOcDADCSAgIAAAABmAJAAMkDACG3AgEAAAABygIBAMgDACHMAkAAyQMAIdgCAQDIAwAh2QICAMcDACHaAgEAyAMAIdsCQADJAwAhAQAAANIBACABAAAA0gEAIAyPAgAA5wMAMJACAADVAQAQkQIAAOcDADCSAgIAxwMAIZgCQADJAwAhtwIBAMgDACHKAgEAyAMAIcwCQADJAwAh2AIBAMgDACHZAgIAxwMAIdoCAQDIAwAh2wJAAMkDACEBmQMAAPkEACADAAAA1QEAIAMAANYBADAEAADSAQAgAwAAANUBACADAADWAQAwBAAA0gEAIAMAAADVAQAgAwAA1gEAMAQAANIBACAJkgICAAAAAZgCQAAAAAG3AgEAAAABygIBAAAAAcwCQAAAAAHYAgEAAAAB2QICAAAAAdoCAQAAAAHbAkAAAAABAQgAANoBACAJkgICAAAAAZgCQAAAAAG3AgEAAAABygIBAAAAAcwCQAAAAAHYAgEAAAAB2QICAAAAAdoCAQAAAAHbAkAAAAABAQgAANwBADAJkgICAI4EACGYAkAAjAQAIbcCAQCWBAAhygIBAJYEACHMAkAAjAQAIdgCAQCWBAAh2QICAI4EACHaAgEAlgQAIdsCQACMBAAhAgAAANIBACAIAADeAQAgCZICAgCOBAAhmAJAAIwEACG3AgEAlgQAIcoCAQCWBAAhzAJAAIwEACHYAgEAlgQAIdkCAgCOBAAh2gIBAJYEACHbAkAAjAQAIQIAAADVAQAgCAAA4AEAIAMAAADSAQAgDQAA2gEAIA4AAN4BACABAAAA0gEAIAEAAADVAQAgBRMAAPQEACAUAAD1BAAgFQAA-AQAIBYAAPcEACAXAAD2BAAgDI8CAADmAwAwkAIAAOYBABCRAgAA5gMAMJICAgCoAwAhmAJAAKkDACG3AgEAtgMAIcoCAQC2AwAhzAJAAKkDACHYAgEAtgMAIdkCAgCoAwAh2gIBALYDACHbAkAAqQMAIQMAAADVAQAgAwAA5QEAMBIAAOYBACADAAAA1QEAIAMAANYBADAEAADSAQAgCI8CAADlAwAwkAIAAOwBABCRAgAA5QMAMJICAgAAAAGYAkAAyQMAIcwCQADJAwAh1gIBAAAAAdcCAgDHAwAhAQAAAOkBACABAAAA6QEAIAiPAgAA5QMAMJACAADsAQAQkQIAAOUDADCSAgIAxwMAIZgCQADJAwAhzAJAAMkDACHWAgEAyAMAIdcCAgDHAwAhAZkDAADzBAAgAwAAAOwBACADAADtAQAwBAAA6QEAIAMAAADsAQAgAwAA7QEAMAQAAOkBACADAAAA7AEAIAMAAO0BADAEAADpAQAgBZICAgAAAAGYAkAAAAABzAJAAAAAAdYCAQAAAAHXAgIAAAABAQgAAPEBACAFkgICAAAAAZgCQAAAAAHMAkAAAAAB1gIBAAAAAdcCAgAAAAEBCAAA8wEAMAWSAgIAjgQAIZgCQACMBAAhzAJAAIwEACHWAgEAlgQAIdcCAgCOBAAhAgAAAOkBACAIAAD1AQAgBZICAgCOBAAhmAJAAIwEACHMAkAAjAQAIdYCAQCWBAAh1wICAI4EACECAAAA7AEAIAgAAPcBACADAAAA6QEAIA0AAPEBACAOAAD1AQAgAQAAAOkBACABAAAA7AEAIAUTAADuBAAgFAAA7wQAIBUAAPIEACAWAADxBAAgFwAA8AQAIAiPAgAA5AMAMJACAAD9AQAQkQIAAOQDADCSAgIAqAMAIZgCQACpAwAhzAJAAKkDACHWAgEAtgMAIdcCAgCoAwAhAwAAAOwBACADAAD8AQAwEgAA_QEAIAMAAADsAQAgAwAA7QEAMAQAAOkBACAKjwIAAOMDADCQAgAAgwIAEJECAADjAwAwkgICAAAAAZgCQADJAwAhzAJAAMkDACHSAgEAAAAB0wIBANQDACHUAkAAyQMAIdUCAADOAwAgAQAAAIACACABAAAAgAIAIAqPAgAA4wMAMJACAACDAgAQkQIAAOMDADCSAgIAxwMAIZgCQADJAwAhzAJAAMkDACHSAgEAyAMAIdMCAQDUAwAh1AJAAMkDACHVAgAAzgMAIAPTAgAAhgQAINUCAACGBAAgmQMAAO0EACADAAAAgwIAIAMAAIQCADAEAACAAgAgAwAAAIMCACADAACEAgAwBAAAgAIAIAMAAACDAgAgAwAAhAIAMAQAAIACACAHkgICAAAAAZgCQAAAAAHMAkAAAAAB0gIBAAAAAdMCAQAAAAHUAkAAAAAB1QKAAAAAAQEIAACIAgAgB5ICAgAAAAGYAkAAAAABzAJAAAAAAdICAQAAAAHTAgEAAAAB1AJAAAAAAdUCgAAAAAEBCAAAigIAMAeSAgIAjgQAIZgCQACMBAAhzAJAAIwEACHSAgEAlgQAIdMCAQCfBAAh1AJAAIwEACHVAoAAAAABAgAAAIACACAIAACMAgAgB5ICAgCOBAAhmAJAAIwEACHMAkAAjAQAIdICAQCWBAAh0wIBAJ8EACHUAkAAjAQAIdUCgAAAAAECAAAAgwIAIAgAAI4CACADAAAAgAIAIA0AAIgCACAOAACMAgAgAQAAAIACACABAAAAgwIAIAcTAADoBAAgFAAA6QQAIBUAAOwEACAWAADrBAAgFwAA6gQAINMCAACGBAAg1QIAAIYEACAKjwIAAOIDADCQAgAAlAIAEJECAADiAwAwkgICAKgDACGYAkAAqQMAIcwCQACpAwAh0gIBALYDACHTAgEAvQMAIdQCQACpAwAh1QIAAKsDACADAAAAgwIAIAMAAJMCADASAACUAgAgAwAAAIMCACADAACEAgAwBAAAgAIAIAuPAgAA3wMAMJACAACaAgAQkQIAAN8DADCSAgIAAAABmAJAAMkDACGnAkAAyQMAIc0CAQDIAwAhzgIBAMgDACHPAggA4AMAIdACAQDIAwAh0QIAAOEDACABAAAAlwIAIAEAAACXAgAgCo8CAADfAwAwkAIAAJoCABCRAgAA3wMAMJICAgDHAwAhmAJAAMkDACGnAkAAyQMAIc0CAQDIAwAhzgIBAMgDACHPAggA4AMAIdACAQDIAwAhAZkDAADnBAAgAwAAAJoCACADAACbAgAwBAAAlwIAIAMAAACaAgAgAwAAmwIAMAQAAJcCACADAAAAmgIAIAMAAJsCADAEAACXAgAgB5ICAgAAAAGYAkAAAAABpwJAAAAAAc0CAQAAAAHOAgEAAAABzwIIAAAAAdACAQAAAAEBCAAAnwIAIAeSAgIAAAABmAJAAAAAAacCQAAAAAHNAgEAAAABzgIBAAAAAc8CCAAAAAHQAgEAAAABAQgAAKECADAHkgICAI4EACGYAkAAjAQAIacCQACMBAAhzQIBAJYEACHOAgEAlgQAIc8CCADmBAAh0AIBAJYEACECAAAAlwIAIAgAAKMCACAHkgICAI4EACGYAkAAjAQAIacCQACMBAAhzQIBAJYEACHOAgEAlgQAIc8CCADmBAAh0AIBAJYEACECAAAAmgIAIAgAAKUCACADAAAAlwIAIA0AAJ8CACAOAACjAgAgAQAAAJcCACABAAAAmgIAIAUTAADhBAAgFAAA4gQAIBUAAOUEACAWAADkBAAgFwAA4wQAIAqPAgAA3AMAMJACAACrAgAQkQIAANwDADCSAgIAqAMAIZgCQACpAwAhpwJAAKkDACHNAgEAtgMAIc4CAQC2AwAhzwIIAN0DACHQAgEAtgMAIQMAAACaAgAgAwAAqgIAMBIAAKsCACADAAAAmgIAIAMAAJsCADAEAACXAgAgCY8CAADbAwAwkAIAALECABCRAgAA2wMAMJICAgAAAAHIAgEAAAAByQIBAMgDACHKAgEAyAMAIcsCAQDIAwAhzAJAAMkDACEBAAAArgIAIAEAAACuAgAgCY8CAADbAwAwkAIAALECABCRAgAA2wMAMJICAgDHAwAhyAIBAMgDACHJAgEAyAMAIcoCAQDIAwAhywIBAMgDACHMAkAAyQMAIQGZAwAA4AQAIAMAAACxAgAgAwAAsgIAMAQAAK4CACADAAAAsQIAIAMAALICADAEAACuAgAgAwAAALECACADAACyAgAwBAAArgIAIAaSAgIAAAAByAIBAAAAAckCAQAAAAHKAgEAAAABywIBAAAAAcwCQAAAAAEBCAAAtgIAIAaSAgIAAAAByAIBAAAAAckCAQAAAAHKAgEAAAABywIBAAAAAcwCQAAAAAEBCAAAuAIAMAaSAgIAjgQAIcgCAQCWBAAhyQIBAJYEACHKAgEAlgQAIcsCAQCWBAAhzAJAAIwEACECAAAArgIAIAgAALoCACAGkgICAI4EACHIAgEAlgQAIckCAQCWBAAhygIBAJYEACHLAgEAlgQAIcwCQACMBAAhAgAAALECACAIAAC8AgAgAwAAAK4CACANAAC2AgAgDgAAugIAIAEAAACuAgAgAQAAALECACAFEwAA2wQAIBQAANwEACAVAADfBAAgFgAA3gQAIBcAAN0EACAJjwIAANoDADCQAgAAwgIAEJECAADaAwAwkgICAKgDACHIAgEAtgMAIckCAQC2AwAhygIBALYDACHLAgEAtgMAIcwCQACpAwAhAwAAALECACADAADBAgAwEgAAwgIAIAMAAACxAgAgAwAAsgIAMAQAAK4CACAI1gEAAMoDACCPAgAAxgMAMJACAADXAgAQkQIAAMYDADCSAgIAAAABmAJAAMkDACG1AgEAAAABtwIBAMgDACEBAAAAxQIAIBfSAQAA1wMAINQBAADYAwAg1QEAANkDACCPAgAA0wMAMJACAADHAgAQkQIAANMDADCSAgIAxwMAIZgCQADJAwAhtAIBAMgDACG1AgEAyAMAIbYCAQDIAwAhtwIBAMgDACG4AgEA1AMAIbkCAgDHAwAhugICAMcDACG7AgIAxwMAIbwCAgDHAwAhvQICAMcDACG-AgIAxwMAIb8CAgDVAwAhwAJAANIDACHBAiAA1gMAIcICQADSAwAhCNIBAADXBAAg1AEAANgEACDVAQAA2QQAILgCAACGBAAgvwIAAIYEACDAAgAAhgQAIMICAACGBAAgmQMAANoEACAX0gEAANcDACDUAQAA2AMAINUBAADZAwAgjwIAANMDADCQAgAAxwIAEJECAADTAwAwkgICAAAAAZgCQADJAwAhtAIBAAAAAbUCAQDIAwAhtgIBAMgDACG3AgEAyAMAIbgCAQDUAwAhuQICAMcDACG6AgIAxwMAIbsCAgDHAwAhvAICAMcDACG9AgIAxwMAIb4CAgDHAwAhvwICANUDACHAAkAA0gMAIcECIADWAwAhwgJAANIDACEDAAAAxwIAIAMAAMgCADAEAADJAgAgE9MBAADPAwAgjwIAANEDADCQAgAAywIAEJECAADRAwAwkgICAMcDACGTAgIAxwMAIZUCCADNAwAhlgIIAM0DACGXAgAAzgMAIJgCQADJAwAhpwJAAMkDACGoAggAzQMAIakCCADNAwAhqgIIAM0DACGrAggAzQMAIawCCADNAwAhrQIIAM0DACGuAgEAyAMAIa8CQADSAwAhDNMBAADVBAAglQIAAIYEACCWAgAAhgQAIJcCAACGBAAgqAIAAIYEACCpAgAAhgQAIKoCAACGBAAgqwIAAIYEACCsAgAAhgQAIK0CAACGBAAgrwIAAIYEACCZAwAA1gQAIBTTAQAAzwMAII8CAADRAwAwkAIAAMsCABCRAgAA0QMAMJICAgAAAAGTAgIAxwMAIZUCCADNAwAhlgIIAM0DACGXAgAAzgMAIJgCQADJAwAhpwJAAMkDACGoAggAzQMAIakCCADNAwAhqgIIAM0DACGrAggAzQMAIawCCADNAwAhrQIIAM0DACGuAgEAyAMAIa8CQADSAwAhxwIAANADACADAAAAywIAIAMAAMwCADAEAADNAgAgC9MBAADPAwAgjwIAAMwDADCQAgAAzwIAEJECAADMAwAwkgICAMcDACGTAgIAxwMAIZQCQADJAwAhlQIIAM0DACGWAggAzQMAIZcCAADOAwAgmAJAAMkDACEE0wEAANUEACCVAgAAhgQAIJYCAACGBAAglwIAAIYEACAM0wEAAM8DACCPAgAAzAMAMJACAADPAgAQkQIAAMwDADCSAgIAAAABkwICAMcDACGUAkAAyQMAIZUCCADNAwAhlgIIAM0DACGXAgAAzgMAIJgCQADJAwAhxgIAAMsDACADAAAAzwIAIAMAANACADAEAADRAgAgAQAAAMsCACABAAAAzwIAIAEAAADHAgAgAQAAAMUCACAI1gEAAMoDACCPAgAAxgMAMJACAADXAgAQkQIAAMYDADCSAgIAxwMAIZgCQADJAwAhtQIBAMgDACG3AgEAyAMAIQLWAQAA0wQAIJkDAADUBAAgAwAAANcCACADAADYAgAwBAAAxQIAIAMAAADXAgAgAwAA2AIAMAQAAMUCACADAAAA1wIAIAMAANgCADAEAADFAgAgBdYBAADSBAAgkgICAAAAAZgCQAAAAAG1AgEAAAABtwIBAAAAAQEIAADcAgAgBJICAgAAAAGYAkAAAAABtQIBAAAAAbcCAQAAAAEBCAAA3gIAMAXWAQAAxQQAIJICAgCOBAAhmAJAAIwEACG1AgEAlgQAIbcCAQCWBAAhAgAAAMUCACAIAADgAgAgBJICAgCOBAAhmAJAAIwEACG1AgEAlgQAIbcCAQCWBAAhAgAAANcCACAIAADiAgAgAwAAAMUCACANAADcAgAgDgAA4AIAIAEAAADFAgAgAQAAANcCACAFEwAAwAQAIBQAAMEEACAVAADEBAAgFgAAwwQAIBcAAMIEACAHjwIAAMUDADCQAgAA6AIAEJECAADFAwAwkgICAKgDACGYAkAAqQMAIbUCAQC2AwAhtwIBALYDACEDAAAA1wIAIAMAAOcCADASAADoAgAgAwAAANcCACADAADYAgAwBAAAxQIAIAEAAADJAgAgAQAAAMkCACADAAAAxwIAIAMAAMgCADAEAADJAgAgAwAAAMcCACADAADIAgAwBAAAyQIAIAMAAADHAgAgAwAAyAIAMAQAAMkCACAU0gEAAL0EACDUAQAAvgQAINUBAAC_BAAgkgICAAAAAZgCQAAAAAG0AgEAAAABtQIBAAAAAbYCAQAAAAG3AgEAAAABuAIBAAAAAbkCAgAAAAG6AgIAAAABuwICAAAAAbwCAgAAAAG9AgIAAAABvgICAAAAAb8CAgAAAAHAAkAAAAABwQIgAAAAAcICQAAAAAEBCAAA8AIAIBGSAgIAAAABmAJAAAAAAbQCAQAAAAG1AgEAAAABtgIBAAAAAbcCAQAAAAG4AgEAAAABuQICAAAAAboCAgAAAAG7AgIAAAABvAICAAAAAb0CAgAAAAG-AgIAAAABvwICAAAAAcACQAAAAAHBAiAAAAABwgJAAAAAAQEIAADyAgAwFNIBAACiBAAg1AEAAKMEACDVAQAApAQAIJICAgCOBAAhmAJAAIwEACG0AgEAlgQAIbUCAQCWBAAhtgIBAJYEACG3AgEAlgQAIbgCAQCfBAAhuQICAI4EACG6AgIAjgQAIbsCAgCOBAAhvAICAI4EACG9AgIAjgQAIb4CAgCOBAAhvwICAKAEACHAAkAAlwQAIcECIAChBAAhwgJAAJcEACECAAAAyQIAIAgAAPQCACARkgICAI4EACGYAkAAjAQAIbQCAQCWBAAhtQIBAJYEACG2AgEAlgQAIbcCAQCWBAAhuAIBAJ8EACG5AgIAjgQAIboCAgCOBAAhuwICAI4EACG8AgIAjgQAIb0CAgCOBAAhvgICAI4EACG_AgIAoAQAIcACQACXBAAhwQIgAKEEACHCAkAAlwQAIQIAAADHAgAgCAAA9gIAIAMAAADJAgAgDQAA8AIAIA4AAPQCACABAAAAyQIAIAEAAADHAgAgCRMAAJoEACAUAACbBAAgFQAAngQAIBYAAJ0EACAXAACcBAAguAIAAIYEACC_AgAAhgQAIMACAACGBAAgwgIAAIYEACAUjwIAALwDADCQAgAA_AIAEJECAAC8AwAwkgICAKgDACGYAkAAqQMAIbQCAQC2AwAhtQIBALYDACG2AgEAtgMAIbcCAQC2AwAhuAIBAL0DACG5AgIAqAMAIboCAgCoAwAhuwICAKgDACG8AgIAqAMAIb0CAgCoAwAhvgICAKgDACG_AgIAvgMAIcACQAC3AwAhwQIgAL8DACHCAkAAtwMAIQMAAADHAgAgAwAA-wIAMBIAAPwCACADAAAAxwIAIAMAAMgCADAEAADJAgAgAQAAAM0CACABAAAAzQIAIAMAAADLAgAgAwAAzAIAMAQAAM0CACADAAAAywIAIAMAAMwCADAEAADNAgAgAwAAAMsCACADAADMAgAwBAAAzQIAIBDTAQAAmQQAIJICAgAAAAGTAgIAAAABlQIIAAAAAZYCCAAAAAGXAoAAAAABmAJAAAAAAacCQAAAAAGoAggAAAABqQIIAAAAAaoCCAAAAAGrAggAAAABrAIIAAAAAa0CCAAAAAGuAgEAAAABrwJAAAAAAQEIAACEAwAgD5ICAgAAAAGTAgIAAAABlQIIAAAAAZYCCAAAAAGXAoAAAAABmAJAAAAAAacCQAAAAAGoAggAAAABqQIIAAAAAaoCCAAAAAGrAggAAAABrAIIAAAAAa0CCAAAAAGuAgEAAAABrwJAAAAAAQEIAACGAwAwENMBAACYBAAgkgICAI4EACGTAgIAjgQAIZUCCACNBAAhlgIIAI0EACGXAoAAAAABmAJAAIwEACGnAkAAjAQAIagCCACNBAAhqQIIAI0EACGqAggAjQQAIasCCACNBAAhrAIIAI0EACGtAggAjQQAIa4CAQCWBAAhrwJAAJcEACECAAAAzQIAIAgAAIgDACAPkgICAI4EACGTAgIAjgQAIZUCCACNBAAhlgIIAI0EACGXAoAAAAABmAJAAIwEACGnAkAAjAQAIagCCACNBAAhqQIIAI0EACGqAggAjQQAIasCCACNBAAhrAIIAI0EACGtAggAjQQAIa4CAQCWBAAhrwJAAJcEACECAAAAywIAIAgAAIoDACADAAAAzQIAIA0AAIQDACAOAACIAwAgAQAAAM0CACABAAAAywIAIA8TAACRBAAgFAAAkgQAIBUAAJUEACAWAACUBAAgFwAAkwQAIJUCAACGBAAglgIAAIYEACCXAgAAhgQAIKgCAACGBAAgqQIAAIYEACCqAgAAhgQAIKsCAACGBAAgrAIAAIYEACCtAgAAhgQAIK8CAACGBAAgEo8CAAC1AwAwkAIAAJADABCRAgAAtQMAMJICAgCoAwAhkwICAKgDACGVAggAqgMAIZYCCACqAwAhlwIAAKsDACCYAkAAqQMAIacCQACpAwAhqAIIAKoDACGpAggAqgMAIaoCCACqAwAhqwIIAKoDACGsAggAqgMAIa0CCACqAwAhrgIBALYDACGvAkAAtwMAIQMAAADLAgAgAwAAjwMAMBIAAJADACADAAAAywIAIAMAAMwCADAEAADNAgAgAQAAANECACABAAAA0QIAIAMAAADPAgAgAwAA0AIAMAQAANECACADAAAAzwIAIAMAANACADAEAADRAgAgAwAAAM8CACADAADQAgAwBAAA0QIAIAjTAQAAkAQAIJICAgAAAAGTAgIAAAABlAJAAAAAAZUCCAAAAAGWAggAAAABlwKAAAAAAZgCQAAAAAEBCAAAmAMAIAeSAgIAAAABkwICAAAAAZQCQAAAAAGVAggAAAABlgIIAAAAAZcCgAAAAAGYAkAAAAABAQgAAJoDADAI0wEAAI8EACCSAgIAjgQAIZMCAgCOBAAhlAJAAIwEACGVAggAjQQAIZYCCACNBAAhlwKAAAAAAZgCQACMBAAhAgAAANECACAIAACcAwAgB5ICAgCOBAAhkwICAI4EACGUAkAAjAQAIZUCCACNBAAhlgIIAI0EACGXAoAAAAABmAJAAIwEACECAAAAzwIAIAgAAJ4DACADAAAA0QIAIA0AAJgDACAOAACcAwAgAQAAANECACABAAAAzwIAIAgTAACHBAAgFAAAiAQAIBUAAIsEACAWAACKBAAgFwAAiQQAIJUCAACGBAAglgIAAIYEACCXAgAAhgQAIAqPAgAApwMAMJACAACkAwAQkQIAAKcDADCSAgIAqAMAIZMCAgCoAwAhlAJAAKkDACGVAggAqgMAIZYCCACqAwAhlwIAAKsDACCYAkAAqQMAIQMAAADPAgAgAwAAowMAMBIAAKQDACADAAAAzwIAIAMAANACADAEAADRAgAgCo8CAACnAwAwkAIAAKQDABCRAgAApwMAMJICAgCoAwAhkwICAKgDACGUAkAAqQMAIZUCCACqAwAhlgIIAKoDACGXAgAAqwMAIJgCQACpAwAhDRMAALEDACAUAAC0AwAgFQAAsQMAIBYAALEDACAXAACxAwAgmQICAAAAAaACAgAAAAGhAgIAAAABogICAAAAAaMCAgAAAAGkAgIAswMAIaUCAgAAAASmAgIAAAAECxMAALEDACAWAACyAwAgFwAAsgMAIJkCQAAAAAGgAkAAAAABoQJAAAAAAaICQAAAAAGjAkAAAAABpAJAALADACGlAkAAAAAEpgJAAAAABA0TAACsAwAgFAAArwMAIBUAAK8DACAWAACvAwAgFwAArwMAIJkCCAAAAAGgAggAAAABoQIIAAAAAaICCAAAAAGjAggAAAABpAIIAK4DACGlAggAAAAFpgIIAAAABQ8TAACsAwAgFgAArQMAIBcAAK0DACCZAoAAAAABmgIBAAAAAZsCAQAAAAGcAgEAAAABnQKAAAAAAZ4CgAAAAAGfAoAAAAABoAKAAAAAAaECgAAAAAGiAoAAAAABowKAAAAAAaQCgAAAAAEImQICAAAAAaACAgAAAAGhAgIAAAABogICAAAAAaMCAgAAAAGkAgIArAMAIaUCAgAAAAWmAgIAAAAFDJkCgAAAAAGaAgEAAAABmwIBAAAAAZwCAQAAAAGdAoAAAAABngKAAAAAAZ8CgAAAAAGgAoAAAAABoQKAAAAAAaICgAAAAAGjAoAAAAABpAKAAAAAAQ0TAACsAwAgFAAArwMAIBUAAK8DACAWAACvAwAgFwAArwMAIJkCCAAAAAGgAggAAAABoQIIAAAAAaICCAAAAAGjAggAAAABpAIIAK4DACGlAggAAAAFpgIIAAAABQiZAggAAAABoAIIAAAAAaECCAAAAAGiAggAAAABowIIAAAAAaQCCACvAwAhpQIIAAAABaYCCAAAAAULEwAAsQMAIBYAALIDACAXAACyAwAgmQJAAAAAAaACQAAAAAGhAkAAAAABogJAAAAAAaMCQAAAAAGkAkAAsAMAIaUCQAAAAASmAkAAAAAECJkCAgAAAAGgAgIAAAABoQICAAAAAaICAgAAAAGjAgIAAAABpAICALEDACGlAgIAAAAEpgICAAAABAiZAkAAAAABoAJAAAAAAaECQAAAAAGiAkAAAAABowJAAAAAAaQCQACyAwAhpQJAAAAABKYCQAAAAAQNEwAAsQMAIBQAALQDACAVAACxAwAgFgAAsQMAIBcAALEDACCZAgIAAAABoAICAAAAAaECAgAAAAGiAgIAAAABowICAAAAAaQCAgCzAwAhpQICAAAABKYCAgAAAAQImQIIAAAAAaACCAAAAAGhAggAAAABogIIAAAAAaMCCAAAAAGkAggAtAMAIaUCCAAAAASmAggAAAAEEo8CAAC1AwAwkAIAAJADABCRAgAAtQMAMJICAgCoAwAhkwICAKgDACGVAggAqgMAIZYCCACqAwAhlwIAAKsDACCYAkAAqQMAIacCQACpAwAhqAIIAKoDACGpAggAqgMAIaoCCACqAwAhqwIIAKoDACGsAggAqgMAIa0CCACqAwAhrgIBALYDACGvAkAAtwMAIQ8TAACxAwAgFgAAuwMAIBcAALsDACCZAgEAAAABoAIBAAAAAaECAQAAAAGiAgEAAAABowIBAAAAAaQCAQC6AwAhpQIBAAAABKYCAQAAAASwAgEAAAABsQIBAAAAAbICAQAAAAGzAgEAAAABCxMAAKwDACAWAAC5AwAgFwAAuQMAIJkCQAAAAAGgAkAAAAABoQJAAAAAAaICQAAAAAGjAkAAAAABpAJAALgDACGlAkAAAAAFpgJAAAAABQsTAACsAwAgFgAAuQMAIBcAALkDACCZAkAAAAABoAJAAAAAAaECQAAAAAGiAkAAAAABowJAAAAAAaQCQAC4AwAhpQJAAAAABaYCQAAAAAUImQJAAAAAAaACQAAAAAGhAkAAAAABogJAAAAAAaMCQAAAAAGkAkAAuQMAIaUCQAAAAAWmAkAAAAAFDxMAALEDACAWAAC7AwAgFwAAuwMAIJkCAQAAAAGgAgEAAAABoQIBAAAAAaICAQAAAAGjAgEAAAABpAIBALoDACGlAgEAAAAEpgIBAAAABLACAQAAAAGxAgEAAAABsgIBAAAAAbMCAQAAAAEMmQIBAAAAAaACAQAAAAGhAgEAAAABogIBAAAAAaMCAQAAAAGkAgEAuwMAIaUCAQAAAASmAgEAAAAEsAIBAAAAAbECAQAAAAGyAgEAAAABswIBAAAAARSPAgAAvAMAMJACAAD8AgAQkQIAALwDADCSAgIAqAMAIZgCQACpAwAhtAIBALYDACG1AgEAtgMAIbYCAQC2AwAhtwIBALYDACG4AgEAvQMAIbkCAgCoAwAhugICAKgDACG7AgIAqAMAIbwCAgCoAwAhvQICAKgDACG-AgIAqAMAIb8CAgC-AwAhwAJAALcDACHBAiAAvwMAIcICQAC3AwAhDxMAAKwDACAWAADEAwAgFwAAxAMAIJkCAQAAAAGgAgEAAAABoQIBAAAAAaICAQAAAAGjAgEAAAABpAIBAMMDACGlAgEAAAAFpgIBAAAABbACAQAAAAGxAgEAAAABsgIBAAAAAbMCAQAAAAENEwAArAMAIBQAAK8DACAVAACsAwAgFgAArAMAIBcAAKwDACCZAgIAAAABoAICAAAAAaECAgAAAAGiAgIAAAABowICAAAAAaQCAgDCAwAhpQICAAAABaYCAgAAAAUFEwAAsQMAIBYAAMEDACAXAADBAwAgmQIgAAAAAaQCIADAAwAhBRMAALEDACAWAADBAwAgFwAAwQMAIJkCIAAAAAGkAiAAwAMAIQKZAiAAAAABpAIgAMEDACENEwAArAMAIBQAAK8DACAVAACsAwAgFgAArAMAIBcAAKwDACCZAgIAAAABoAICAAAAAaECAgAAAAGiAgIAAAABowICAAAAAaQCAgDCAwAhpQICAAAABaYCAgAAAAUPEwAArAMAIBYAAMQDACAXAADEAwAgmQIBAAAAAaACAQAAAAGhAgEAAAABogIBAAAAAaMCAQAAAAGkAgEAwwMAIaUCAQAAAAWmAgEAAAAFsAIBAAAAAbECAQAAAAGyAgEAAAABswIBAAAAAQyZAgEAAAABoAIBAAAAAaECAQAAAAGiAgEAAAABowIBAAAAAaQCAQDEAwAhpQIBAAAABaYCAQAAAAWwAgEAAAABsQIBAAAAAbICAQAAAAGzAgEAAAABB48CAADFAwAwkAIAAOgCABCRAgAAxQMAMJICAgCoAwAhmAJAAKkDACG1AgEAtgMAIbcCAQC2AwAhCNYBAADKAwAgjwIAAMYDADCQAgAA1wIAEJECAADGAwAwkgICAMcDACGYAkAAyQMAIbUCAQDIAwAhtwIBAMgDACEImQICAAAAAaACAgAAAAGhAgIAAAABogICAAAAAaMCAgAAAAGkAgIAsQMAIaUCAgAAAASmAgIAAAAEDJkCAQAAAAGgAgEAAAABoQIBAAAAAaICAQAAAAGjAgEAAAABpAIBALsDACGlAgEAAAAEpgIBAAAABLACAQAAAAGxAgEAAAABsgIBAAAAAbMCAQAAAAEImQJAAAAAAaACQAAAAAGhAkAAAAABogJAAAAAAaMCQAAAAAGkAkAAsgMAIaUCQAAAAASmAkAAAAAEA8MCAADHAgAgxAIAAMcCACDFAgAAxwIAIAKTAgIAAAABlAJAAAAAAQvTAQAAzwMAII8CAADMAwAwkAIAAM8CABCRAgAAzAMAMJICAgDHAwAhkwICAMcDACGUAkAAyQMAIZUCCADNAwAhlgIIAM0DACGXAgAAzgMAIJgCQADJAwAhCJkCCAAAAAGgAggAAAABoQIIAAAAAaICCAAAAAGjAggAAAABpAIIAK8DACGlAggAAAAFpgIIAAAABQyZAoAAAAABmgIBAAAAAZsCAQAAAAGcAgEAAAABnQKAAAAAAZ4CgAAAAAGfAoAAAAABoAKAAAAAAaECgAAAAAGiAoAAAAABowKAAAAAAaQCgAAAAAEZ0gEAANcDACDUAQAA2AMAINUBAADZAwAgjwIAANMDADCQAgAAxwIAEJECAADTAwAwkgICAMcDACGYAkAAyQMAIbQCAQDIAwAhtQIBAMgDACG2AgEAyAMAIbcCAQDIAwAhuAIBANQDACG5AgIAxwMAIboCAgDHAwAhuwICAMcDACG8AgIAxwMAIb0CAgDHAwAhvgICAMcDACG_AgIA1QMAIcACQADSAwAhwQIgANYDACHCAkAA0gMAIY4DAADHAgAgjwMAAMcCACACkwICAAAAAacCQAAAAAET0wEAAM8DACCPAgAA0QMAMJACAADLAgAQkQIAANEDADCSAgIAxwMAIZMCAgDHAwAhlQIIAM0DACGWAggAzQMAIZcCAADOAwAgmAJAAMkDACGnAkAAyQMAIagCCADNAwAhqQIIAM0DACGqAggAzQMAIasCCADNAwAhrAIIAM0DACGtAggAzQMAIa4CAQDIAwAhrwJAANIDACEImQJAAAAAAaACQAAAAAGhAkAAAAABogJAAAAAAaMCQAAAAAGkAkAAuQMAIaUCQAAAAAWmAkAAAAAFF9IBAADXAwAg1AEAANgDACDVAQAA2QMAII8CAADTAwAwkAIAAMcCABCRAgAA0wMAMJICAgDHAwAhmAJAAMkDACG0AgEAyAMAIbUCAQDIAwAhtgIBAMgDACG3AgEAyAMAIbgCAQDUAwAhuQICAMcDACG6AgIAxwMAIbsCAgDHAwAhvAICAMcDACG9AgIAxwMAIb4CAgDHAwAhvwICANUDACHAAkAA0gMAIcECIADWAwAhwgJAANIDACEMmQIBAAAAAaACAQAAAAGhAgEAAAABogIBAAAAAaMCAQAAAAGkAgEAxAMAIaUCAQAAAAWmAgEAAAAFsAIBAAAAAbECAQAAAAGyAgEAAAABswIBAAAAAQiZAgIAAAABoAICAAAAAaECAgAAAAGiAgIAAAABowICAAAAAaQCAgCsAwAhpQICAAAABaYCAgAAAAUCmQIgAAAAAaQCIADBAwAhCtYBAADKAwAgjwIAAMYDADCQAgAA1wIAEJECAADGAwAwkgICAMcDACGYAkAAyQMAIbUCAQDIAwAhtwIBAMgDACGOAwAA1wIAII8DAADXAgAgA8MCAADLAgAgxAIAAMsCACDFAgAAywIAIAPDAgAAzwIAIMQCAADPAgAgxQIAAM8CACAJjwIAANoDADCQAgAAwgIAEJECAADaAwAwkgICAKgDACHIAgEAtgMAIckCAQC2AwAhygIBALYDACHLAgEAtgMAIcwCQACpAwAhCY8CAADbAwAwkAIAALECABCRAgAA2wMAMJICAgDHAwAhyAIBAMgDACHJAgEAyAMAIcoCAQDIAwAhywIBAMgDACHMAkAAyQMAIQqPAgAA3AMAMJACAACrAgAQkQIAANwDADCSAgIAqAMAIZgCQACpAwAhpwJAAKkDACHNAgEAtgMAIc4CAQC2AwAhzwIIAN0DACHQAgEAtgMAIQ0TAACxAwAgFAAAtAMAIBUAALQDACAWAAC0AwAgFwAAtAMAIJkCCAAAAAGgAggAAAABoQIIAAAAAaICCAAAAAGjAggAAAABpAIIAN4DACGlAggAAAAEpgIIAAAABA0TAACxAwAgFAAAtAMAIBUAALQDACAWAAC0AwAgFwAAtAMAIJkCCAAAAAGgAggAAAABoQIIAAAAAaICCAAAAAGjAggAAAABpAIIAN4DACGlAggAAAAEpgIIAAAABAqPAgAA3wMAMJACAACaAgAQkQIAAN8DADCSAgIAxwMAIZgCQADJAwAhpwJAAMkDACHNAgEAyAMAIc4CAQDIAwAhzwIIAOADACHQAgEAyAMAIQiZAggAAAABoAIIAAAAAaECCAAAAAGiAggAAAABowIIAAAAAaQCCAC0AwAhpQIIAAAABKYCCAAAAAQDpwJAAAAAAc0CAQAAAAHOAgEAAAABCo8CAADiAwAwkAIAAJQCABCRAgAA4gMAMJICAgCoAwAhmAJAAKkDACHMAkAAqQMAIdICAQC2AwAh0wIBAL0DACHUAkAAqQMAIdUCAACrAwAgCo8CAADjAwAwkAIAAIMCABCRAgAA4wMAMJICAgDHAwAhmAJAAMkDACHMAkAAyQMAIdICAQDIAwAh0wIBANQDACHUAkAAyQMAIdUCAADOAwAgCI8CAADkAwAwkAIAAP0BABCRAgAA5AMAMJICAgCoAwAhmAJAAKkDACHMAkAAqQMAIdYCAQC2AwAh1wICAKgDACEIjwIAAOUDADCQAgAA7AEAEJECAADlAwAwkgICAMcDACGYAkAAyQMAIcwCQADJAwAh1gIBAMgDACHXAgIAxwMAIQyPAgAA5gMAMJACAADmAQAQkQIAAOYDADCSAgIAqAMAIZgCQACpAwAhtwIBALYDACHKAgEAtgMAIcwCQACpAwAh2AIBALYDACHZAgIAqAMAIdoCAQC2AwAh2wJAAKkDACEMjwIAAOcDADCQAgAA1QEAEJECAADnAwAwkgICAMcDACGYAkAAyQMAIbcCAQDIAwAhygIBAMgDACHMAkAAyQMAIdgCAQDIAwAh2QICAMcDACHaAgEAyAMAIdsCQADJAwAhD48CAADoAwAwkAIAAM8BABCRAgAA6AMAMJICAgCoAwAhmAJAAKkDACHcAgEAtgMAId0CQACpAwAh3gIIAN0DACHfAgQA6QMAIeACCADdAwAh4QIEAOkDACHiAgQA6QMAIeMCBADpAwAh5AICAKgDACHlAgIAqAMAIQ0TAACxAwAgFAAAtAMAIBUAAOsDACAWAADrAwAgFwAA6wMAIJkCBAAAAAGgAgQAAAABoQIEAAAAAaICBAAAAAGjAgQAAAABpAIEAOoDACGlAgQAAAAEpgIEAAAABA0TAACxAwAgFAAAtAMAIBUAAOsDACAWAADrAwAgFwAA6wMAIJkCBAAAAAGgAgQAAAABoQIEAAAAAaICBAAAAAGjAgQAAAABpAIEAOoDACGlAgQAAAAEpgIEAAAABAiZAgQAAAABoAIEAAAAAaECBAAAAAGiAgQAAAABowIEAAAAAaQCBADrAwAhpQIEAAAABKYCBAAAAAQPjwIAAOwDADCQAgAAvgEAEJECAADsAwAwkgICAMcDACGYAkAAyQMAIdwCAQDIAwAh3QJAAMkDACHeAggA4AMAId8CBADtAwAh4AIIAOADACHhAgQA7QMAIeICBADtAwAh4wIEAO0DACHkAgIAxwMAIeUCAgDHAwAhCJkCBAAAAAGgAgQAAAABoQIEAAAAAaICBAAAAAGjAgQAAAABpAIEAOsDACGlAgQAAAAEpgIEAAAABALcAgEAAAAB3QJAAAAAARyPAgAA7wMAMJACAAC4AQAQkQIAAO8DADCSAgIAqAMAIZgCQACpAwAhtwIBALYDACHKAgEAtgMAIcwCQACpAwAh3AIBALYDACHiAgQA6QMAIeMCBADpAwAh5AICAKgDACHlAgIAqAMAIecCAQC2AwAh6AIBALYDACHpAgEAtgMAIeoCBADpAwAh6wIEAOkDACHsAgIAqAMAIe0CCADdAwAh7gIEAOkDACHvAgQA6QMAIfACCADdAwAh8QIIAN0DACHyAggA3QMAIfMCBADpAwAh9AIEAOkDACH1AkAAqQMAIRyPAgAA8AMAMJACAACnAQAQkQIAAPADADCSAgIAxwMAIZgCQADJAwAhtwIBAMgDACHKAgEAyAMAIcwCQADJAwAh3AIBAMgDACHiAgQA7QMAIeMCBADtAwAh5AICAMcDACHlAgIAxwMAIecCAQDIAwAh6AIBAMgDACHpAgEAyAMAIeoCBADtAwAh6wIEAO0DACHsAgIAxwMAIe0CCADgAwAh7gIEAO0DACHvAgQA7QMAIfACCADgAwAh8QIIAOADACHyAggA4AMAIfMCBADtAwAh9AIEAO0DACH1AkAAyQMAIQ2PAgAA8QMAMJACAAChAQAQkQIAAPEDADCSAgIAqAMAIZgCQACpAwAh3QIBALYDACH2AgEAtgMAIfcCCADdAwAh-AIIAN0DACH5AgEAtgMAIfoCAQC2AwAh-wIIAN0DACH8AggA3QMAIQ2PAgAA8gMAMJACAACQAQAQkQIAAPIDADCSAgIAxwMAIZgCQADJAwAh3QIBAMgDACH2AgEAyAMAIfcCCADgAwAh-AIIAOADACH5AgEAyAMAIfoCAQDIAwAh-wIIAOADACH8AggA4AMAIQyPAgAA8wMAMJACAACKAQAQkQIAAPMDADCSAgIAqAMAIZgCQACpAwAh0AIBALYDACHYAgEAtgMAIf0CAQC2AwAh_gIBALYDACH_AgEAvQMAIYADQACpAwAhgQMCAKgDACEMjwIAAPQDADCQAgAAeQAQkQIAAPQDADCSAgIAxwMAIZgCQADJAwAh0AIBAMgDACHYAgEAyAMAIf0CAQDIAwAh_gIBAMgDACH_AgEA1AMAIYADQADJAwAhgQMCAMcDACEC_QIBAAAAAYADQAAAAAEJjwIAAPYDADCQAgAAcwAQkQIAAPYDADCSAgIAqAMAIZgCQACpAwAhzAJAAKkDACHdAgEAtgMAIYMDAgCoAwAhhAMIAN0DACENjwIAAPcDADCQAgAAXwAQkQIAAPcDADCSAgIAqAMAIZgCQACpAwAhtwIBALYDACHMAkAAqQMAIdACAQC2AwAh_QIBALYDACGFAwEAtgMAIYYDAQC2AwAhhwMBALYDACGIAyAAvwMAIQ43AAD5AwAgjwIAAPgDADCQAgAATgAQkQIAAPgDADCSAgIAxwMAIZgCQADJAwAhtwIBAMgDACHMAkAAyQMAIdACAQDIAwAh_QIBAMgDACGFAwEAyAMAIYYDAQDIAwAhhwMBAMgDACGIAyAA1gMAIQPDAgAASAAgxAIAAEgAIMUCAABIACAC3QIBAAAAAYMDAgAAAAEKNgAA_AMAII8CAAD7AwAwkAIAAEgAEJECAAD7AwAwkgICAMcDACGYAkAAyQMAIcwCQADJAwAh3QIBAMgDACGDAwIAxwMAIYQDCADgAwAhEDcAAPkDACCPAgAA-AMAMJACAABOABCRAgAA-AMAMJICAgDHAwAhmAJAAMkDACG3AgEAyAMAIcwCQADJAwAh0AIBAMgDACH9AgEAyAMAIYUDAQDIAwAhhgMBAMgDACGHAwEAyAMAIYgDIADWAwAhjgMAAE4AII8DAABOACAJCAAA_gMAII8CAAD9AwAwkAIAAEMAEJECAAD9AwAwkgICAKgDACGYAkAAqQMAIbcCAQC2AwAhzAJAAKkDACGKAwEAtgMAIQ8TAACxAwAgFgAA_wMAIBcAAP8DACCZAoAAAAABmgIBAAAAAZsCAQAAAAGcAgEAAAABnQKAAAAAAZ4CgAAAAAGfAoAAAAABoAKAAAAAAaECgAAAAAGiAoAAAAABowKAAAAAAaQCgAAAAAEMmQKAAAAAAZoCAQAAAAGbAgEAAAABnAIBAAAAAZ0CgAAAAAGeAoAAAAABnwKAAAAAAaACgAAAAAGhAoAAAAABogKAAAAAAaMCgAAAAAGkAoAAAAABCQgAAIEEACCPAgAAgAQAMJACAAAyABCRAgAAgAQAMJICAgDHAwAhmAJAAMkDACG3AgEAyAMAIcwCQADJAwAhigMBAMgDACEMmQKAAAAAAZoCAQAAAAGbAgEAAAABnAIBAAAAAZ0CgAAAAAGeAoAAAAABnwKAAAAAAaACgAAAAAGhAoAAAAABogKAAAAAAaMCgAAAAAGkAoAAAAABCo8CAACCBAAwkAIAACwAEJECAACCBAAwkgICAKgDACGYAkAAqQMAIacCAQC2AwAhzAJAAKkDACH9AgEAtgMAIf8CAQC2AwAhiwMBALYDACEKjwIAAIMEADCQAgAAGwAQkQIAAIMEADCSAgIAxwMAIZgCQADJAwAhpwIBAMgDACHMAkAAyQMAIf0CAQDIAwAh_wIBAMgDACGLAwEAyAMAIQmPAgAAhAQAMJACAAAVABCRAgAAhAQAMJICAgCoAwAhmAJAAKkDACG3AgEAtgMAIcwCQACpAwAhjAMBALYDACGNAwEAtgMAIQmPAgAAhQQAMJACAAAEABCRAgAAhQQAMJICAgDHAwAhmAJAAMkDACG3AgEAyAMAIcwCQADJAwAhjAMBAMgDACGNAwEAyAMAIQAAAAAAAAGTA0AAAAABBZMDCAAAAAGaAwgAAAABmwMIAAAAAZwDCAAAAAGdAwgAAAABBZMDAgAAAAGaAwIAAAABmwMCAAAAAZwDAgAAAAGdAwIAAAABBQ0AANYFACAOAADZBQAgkAMAANcFACCRAwAA2AUAIJYDAADJAgAgAw0AANYFACCQAwAA1wUAIJYDAADJAgAgAAAAAAABkwMBAAAAAQGTA0AAAAABBQ0AANEFACAOAADUBQAgkAMAANIFACCRAwAA0wUAIJYDAADJAgAgAw0AANEFACCQAwAA0gUAIJYDAADJAgAgAAAAAAABkwMBAAAAAQWTAwIAAAABmgMCAAAAAZsDAgAAAAGcAwIAAAABnQMCAAAAAQGTAyAAAAABBQ0AAMoFACAOAADPBQAgkAMAAMsFACCRAwAAzgUAIJYDAADFAgAgCw0AALEEADAOAAC2BAAwkAMAALIEADCRAwAAswQAMJIDAAC0BAAgkwMAALUEADCUAwAAtQQAMJUDAAC1BAAwlgMAALUEADCXAwAAtwQAMJgDAAC4BAAwCw0AAKUEADAOAACqBAAwkAMAAKYEADCRAwAApwQAMJIDAACoBAAgkwMAAKkEADCUAwAAqQQAMJUDAACpBAAwlgMAAKkEADCXAwAAqwQAMJgDAACsBAAwBpICAgAAAAGUAkAAAAABlQIIAAAAAZYCCAAAAAGXAoAAAAABmAJAAAAAAQIAAADRAgAgDQAAsAQAIAMAAADRAgAgDQAAsAQAIA4AAK8EACABCAAAzQUAMAzTAQAAzwMAII8CAADMAwAwkAIAAM8CABCRAgAAzAMAMJICAgAAAAGTAgIAxwMAIZQCQADJAwAhlQIIAM0DACGWAggAzQMAIZcCAADOAwAgmAJAAMkDACHGAgAAywMAIAIAAADRAgAgCAAArwQAIAIAAACtBAAgCAAArgQAIAqPAgAArAQAMJACAACtBAAQkQIAAKwEADCSAgIAxwMAIZMCAgDHAwAhlAJAAMkDACGVAggAzQMAIZYCCADNAwAhlwIAAM4DACCYAkAAyQMAIQqPAgAArAQAMJACAACtBAAQkQIAAKwEADCSAgIAxwMAIZMCAgDHAwAhlAJAAMkDACGVAggAzQMAIZYCCADNAwAhlwIAAM4DACCYAkAAyQMAIQaSAgIAjgQAIZQCQACMBAAhlQIIAI0EACGWAggAjQQAIZcCgAAAAAGYAkAAjAQAIQaSAgIAjgQAIZQCQACMBAAhlQIIAI0EACGWAggAjQQAIZcCgAAAAAGYAkAAjAQAIQaSAgIAAAABlAJAAAAAAZUCCAAAAAGWAggAAAABlwKAAAAAAZgCQAAAAAEOkgICAAAAAZUCCAAAAAGWAggAAAABlwKAAAAAAZgCQAAAAAGnAkAAAAABqAIIAAAAAakCCAAAAAGqAggAAAABqwIIAAAAAawCCAAAAAGtAggAAAABrgIBAAAAAa8CQAAAAAECAAAAzQIAIA0AALwEACADAAAAzQIAIA0AALwEACAOAAC7BAAgAQgAAMwFADAU0wEAAM8DACCPAgAA0QMAMJACAADLAgAQkQIAANEDADCSAgIAAAABkwICAMcDACGVAggAzQMAIZYCCADNAwAhlwIAAM4DACCYAkAAyQMAIacCQADJAwAhqAIIAM0DACGpAggAzQMAIaoCCADNAwAhqwIIAM0DACGsAggAzQMAIa0CCADNAwAhrgIBAMgDACGvAkAA0gMAIccCAADQAwAgAgAAAM0CACAIAAC7BAAgAgAAALkEACAIAAC6BAAgEo8CAAC4BAAwkAIAALkEABCRAgAAuAQAMJICAgDHAwAhkwICAMcDACGVAggAzQMAIZYCCADNAwAhlwIAAM4DACCYAkAAyQMAIacCQADJAwAhqAIIAM0DACGpAggAzQMAIaoCCADNAwAhqwIIAM0DACGsAggAzQMAIa0CCADNAwAhrgIBAMgDACGvAkAA0gMAIRKPAgAAuAQAMJACAAC5BAAQkQIAALgEADCSAgIAxwMAIZMCAgDHAwAhlQIIAM0DACGWAggAzQMAIZcCAADOAwAgmAJAAMkDACGnAkAAyQMAIagCCADNAwAhqQIIAM0DACGqAggAzQMAIasCCADNAwAhrAIIAM0DACGtAggAzQMAIa4CAQDIAwAhrwJAANIDACEOkgICAI4EACGVAggAjQQAIZYCCACNBAAhlwKAAAAAAZgCQACMBAAhpwJAAIwEACGoAggAjQQAIakCCACNBAAhqgIIAI0EACGrAggAjQQAIawCCACNBAAhrQIIAI0EACGuAgEAlgQAIa8CQACXBAAhDpICAgCOBAAhlQIIAI0EACGWAggAjQQAIZcCgAAAAAGYAkAAjAQAIacCQACMBAAhqAIIAI0EACGpAggAjQQAIaoCCACNBAAhqwIIAI0EACGsAggAjQQAIa0CCACNBAAhrgIBAJYEACGvAkAAlwQAIQ6SAgIAAAABlQIIAAAAAZYCCAAAAAGXAoAAAAABmAJAAAAAAacCQAAAAAGoAggAAAABqQIIAAAAAaoCCAAAAAGrAggAAAABrAIIAAAAAa0CCAAAAAGuAgEAAAABrwJAAAAAAQMNAADKBQAgkAMAAMsFACCWAwAAxQIAIAQNAACxBAAwkAMAALIEADCSAwAAtAQAIJYDAAC1BAAwBA0AAKUEADCQAwAApgQAMJIDAACoBAAglgMAAKkEADAAAAAAAAsNAADGBAAwDgAAywQAMJADAADHBAAwkQMAAMgEADCSAwAAyQQAIJMDAADKBAAwlAMAAMoEADCVAwAAygQAMJYDAADKBAAwlwMAAMwEADCYAwAAzQQAMBLUAQAAvgQAINUBAAC_BAAgkgICAAAAAZgCQAAAAAG0AgEAAAABtgIBAAAAAbcCAQAAAAG4AgEAAAABuQICAAAAAboCAgAAAAG7AgIAAAABvAICAAAAAb0CAgAAAAG-AgIAAAABvwICAAAAAcACQAAAAAHBAiAAAAABwgJAAAAAAQIAAADJAgAgDQAA0QQAIAMAAADJAgAgDQAA0QQAIA4AANAEACABCAAAyQUAMBfSAQAA1wMAINQBAADYAwAg1QEAANkDACCPAgAA0wMAMJACAADHAgAQkQIAANMDADCSAgIAAAABmAJAAMkDACG0AgEAAAABtQIBAMgDACG2AgEAyAMAIbcCAQDIAwAhuAIBANQDACG5AgIAxwMAIboCAgDHAwAhuwICAMcDACG8AgIAxwMAIb0CAgDHAwAhvgICAMcDACG_AgIA1QMAIcACQADSAwAhwQIgANYDACHCAkAA0gMAIQIAAADJAgAgCAAA0AQAIAIAAADOBAAgCAAAzwQAIBSPAgAAzQQAMJACAADOBAAQkQIAAM0EADCSAgIAxwMAIZgCQADJAwAhtAIBAMgDACG1AgEAyAMAIbYCAQDIAwAhtwIBAMgDACG4AgEA1AMAIbkCAgDHAwAhugICAMcDACG7AgIAxwMAIbwCAgDHAwAhvQICAMcDACG-AgIAxwMAIb8CAgDVAwAhwAJAANIDACHBAiAA1gMAIcICQADSAwAhFI8CAADNBAAwkAIAAM4EABCRAgAAzQQAMJICAgDHAwAhmAJAAMkDACG0AgEAyAMAIbUCAQDIAwAhtgIBAMgDACG3AgEAyAMAIbgCAQDUAwAhuQICAMcDACG6AgIAxwMAIbsCAgDHAwAhvAICAMcDACG9AgIAxwMAIb4CAgDHAwAhvwICANUDACHAAkAA0gMAIcECIADWAwAhwgJAANIDACEQkgICAI4EACGYAkAAjAQAIbQCAQCWBAAhtgIBAJYEACG3AgEAlgQAIbgCAQCfBAAhuQICAI4EACG6AgIAjgQAIbsCAgCOBAAhvAICAI4EACG9AgIAjgQAIb4CAgCOBAAhvwICAKAEACHAAkAAlwQAIcECIAChBAAhwgJAAJcEACES1AEAAKMEACDVAQAApAQAIJICAgCOBAAhmAJAAIwEACG0AgEAlgQAIbYCAQCWBAAhtwIBAJYEACG4AgEAnwQAIbkCAgCOBAAhugICAI4EACG7AgIAjgQAIbwCAgCOBAAhvQICAI4EACG-AgIAjgQAIb8CAgCgBAAhwAJAAJcEACHBAiAAoQQAIcICQACXBAAhEtQBAAC-BAAg1QEAAL8EACCSAgIAAAABmAJAAAAAAbQCAQAAAAG2AgEAAAABtwIBAAAAAbgCAQAAAAG5AgIAAAABugICAAAAAbsCAgAAAAG8AgIAAAABvQICAAAAAb4CAgAAAAG_AgIAAAABwAJAAAAAAcECIAAAAAHCAkAAAAABBA0AAMYEADCQAwAAxwQAMJIDAADJBAAglgMAAMoEADAAAbMCAQAAAAEI0gEAANcEACDUAQAA2AQAINUBAADZBAAguAIAAIYEACC_AgAAhgQAIMACAACGBAAgwgIAAIYEACCZAwAA2gQAIAGzAgEAAAABAtYBAADTBAAgmQMAANQEACAAAAGzAgEAAAABAAAAAAABswIBAAAAAQAAAAAABZMDCAAAAAGaAwgAAAABmwMIAAAAAZwDCAAAAAGdAwgAAAABAbMCAQAAAAEAAAAAAAGzAgEAAAABAAAAAAABswIBAAAAAQAAAAAAAbMCAQAAAAEAAAAAAAWTAwQAAAABmgMEAAAAAZsDBAAAAAGcAwQAAAABnQMEAAAAAQGzAgEAAAABAAAAAAABswIBAAAAAQAAAAAAAbMCAQAAAAEAAAAAAAGzAgEAAAABAAAAAAAFDQAAxAUAIA4AAMcFACCQAwAAxQUAIJEDAADGBQAglgMAAEYAIAMNAADEBQAgkAMAAMUFACCWAwAARgAgAAAAAAALDQAAoAUAMA4AAKUFADCQAwAAoQUAMJEDAACiBQAwkgMAAKMFACCTAwAApAUAMJQDAACkBQAwlQMAAKQFADCWAwAApAUAMJcDAACmBQAwmAMAAKcFADAFkgICAAAAAZgCQAAAAAHMAkAAAAAB3QIBAAAAAYQDCAAAAAECAAAASgAgDQAAqwUAIAMAAABKACANAACrBQAgDgAAqgUAIAEIAADDBQAwCzYAAPwDACCPAgAA-wMAMJACAABIABCRAgAA-wMAMJICAgAAAAGYAkAAyQMAIcwCQADJAwAh3QIBAMgDACGDAwIAxwMAIYQDCADgAwAhiQMAAPoDACACAAAASgAgCAAAqgUAIAIAAACoBQAgCAAAqQUAIAmPAgAApwUAMJACAACoBQAQkQIAAKcFADCSAgIAxwMAIZgCQADJAwAhzAJAAMkDACHdAgEAyAMAIYMDAgDHAwAhhAMIAOADACEJjwIAAKcFADCQAgAAqAUAEJECAACnBQAwkgICAMcDACGYAkAAyQMAIcwCQADJAwAh3QIBAMgDACGDAwIAxwMAIYQDCADgAwAhBZICAgCOBAAhmAJAAIwEACHMAkAAjAQAId0CAQCWBAAhhAMIAOYEACEFkgICAI4EACGYAkAAjAQAIcwCQACMBAAh3QIBAJYEACGEAwgA5gQAIQWSAgIAAAABmAJAAAAAAcwCQAAAAAHdAgEAAAABhAMIAAAAAQQNAACgBQAwkAMAAKEFADCSAwAAowUAIJYDAACkBQAwAAGzAgEAAAABAjcAAK0FACCZAwAArgUAIAGzAgEAAAABAAAAAAABswIBAAAAAQAAAAAAAbMCAQAAAAEAAAAAAAGzAgEAAAABBZICAgAAAAGYAkAAAAABzAJAAAAAAd0CAQAAAAGEAwgAAAABCpICAgAAAAGYAkAAAAABtwIBAAAAAcwCQAAAAAHQAgEAAAAB_QIBAAAAAYUDAQAAAAGGAwEAAAABhwMBAAAAAYgDIAAAAAECAAAARgAgDQAAxAUAIAMAAABOACANAADEBQAgDgAAyAUAIAwAAABOACAIAADIBQAgkgICAI4EACGYAkAAjAQAIbcCAQCWBAAhzAJAAIwEACHQAgEAlgQAIf0CAQCWBAAhhQMBAJYEACGGAwEAlgQAIYcDAQCWBAAhiAMgAKEEACEKkgICAI4EACGYAkAAjAQAIbcCAQCWBAAhzAJAAIwEACHQAgEAlgQAIf0CAQCWBAAhhQMBAJYEACGGAwEAlgQAIYcDAQCWBAAhiAMgAKEEACEQkgICAAAAAZgCQAAAAAG0AgEAAAABtgIBAAAAAbcCAQAAAAG4AgEAAAABuQICAAAAAboCAgAAAAG7AgIAAAABvAICAAAAAb0CAgAAAAG-AgIAAAABvwICAAAAAcACQAAAAAHBAiAAAAABwgJAAAAAAQSSAgIAAAABmAJAAAAAAbUCAQAAAAG3AgEAAAABAgAAAMUCACANAADKBQAgDpICAgAAAAGVAggAAAABlgIIAAAAAZcCgAAAAAGYAkAAAAABpwJAAAAAAagCCAAAAAGpAggAAAABqgIIAAAAAasCCAAAAAGsAggAAAABrQIIAAAAAa4CAQAAAAGvAkAAAAABBpICAgAAAAGUAkAAAAABlQIIAAAAAZYCCAAAAAGXAoAAAAABmAJAAAAAAQMAAADXAgAgDQAAygUAIA4AANAFACAGAAAA1wIAIAgAANAFACCSAgIAjgQAIZgCQACMBAAhtQIBAJYEACG3AgEAlgQAIQSSAgIAjgQAIZgCQACMBAAhtQIBAJYEACG3AgEAlgQAIRPSAQAAvQQAINUBAAC_BAAgkgICAAAAAZgCQAAAAAG0AgEAAAABtQIBAAAAAbYCAQAAAAG3AgEAAAABuAIBAAAAAbkCAgAAAAG6AgIAAAABuwICAAAAAbwCAgAAAAG9AgIAAAABvgICAAAAAb8CAgAAAAHAAkAAAAABwQIgAAAAAcICQAAAAAECAAAAyQIAIA0AANEFACADAAAAxwIAIA0AANEFACAOAADVBQAgFQAAAMcCACAIAADVBQAg0gEAAKIEACDVAQAApAQAIJICAgCOBAAhmAJAAIwEACG0AgEAlgQAIbUCAQCWBAAhtgIBAJYEACG3AgEAlgQAIbgCAQCfBAAhuQICAI4EACG6AgIAjgQAIbsCAgCOBAAhvAICAI4EACG9AgIAjgQAIb4CAgCOBAAhvwICAKAEACHAAkAAlwQAIcECIAChBAAhwgJAAJcEACET0gEAAKIEACDVAQAApAQAIJICAgCOBAAhmAJAAIwEACG0AgEAlgQAIbUCAQCWBAAhtgIBAJYEACG3AgEAlgQAIbgCAQCfBAAhuQICAI4EACG6AgIAjgQAIbsCAgCOBAAhvAICAI4EACG9AgIAjgQAIb4CAgCOBAAhvwICAKAEACHAAkAAlwQAIcECIAChBAAhwgJAAJcEACET0gEAAL0EACDUAQAAvgQAIJICAgAAAAGYAkAAAAABtAIBAAAAAbUCAQAAAAG2AgEAAAABtwIBAAAAAbgCAQAAAAG5AgIAAAABugICAAAAAbsCAgAAAAG8AgIAAAABvQICAAAAAb4CAgAAAAG_AgIAAAABwAJAAAAAAcECIAAAAAHCAkAAAAABAgAAAMkCACANAADWBQAgAwAAAMcCACANAADWBQAgDgAA2gUAIBUAAADHAgAgCAAA2gUAINIBAACiBAAg1AEAAKMEACCSAgIAjgQAIZgCQACMBAAhtAIBAJYEACG1AgEAlgQAIbYCAQCWBAAhtwIBAJYEACG4AgEAnwQAIbkCAgCOBAAhugICAI4EACG7AgIAjgQAIbwCAgCOBAAhvQICAI4EACG-AgIAjgQAIb8CAgCgBAAhwAJAAJcEACHBAiAAoQQAIcICQACXBAAhE9IBAACiBAAg1AEAAKMEACCSAgIAjgQAIZgCQACMBAAhtAIBAJYEACG1AgEAlgQAIbYCAQCWBAAhtwIBAJYEACG4AgEAnwQAIbkCAgCOBAAhugICAI4EACG7AgIAjgQAIbwCAgCOBAAhvQICAI4EACG-AgIAjgQAIb8CAgCgBAAhwAJAAJcEACHBAiAAoQQAIcICQACXBAAhAAAFEwAEFAAFFQAGFgAHFwAIAAAAAAAFEwAEFAAFFQAGFgAHFwAIAAUTAAwUAA0VAA4WAA8XABAAAAAAAAUTAAwUAA0VAA4WAA8XABAABRMAFBQAFRUAFhYAFxcAGAAAAAAABRMAFBQAFRUAFhYAFxcAGAITABw3SxsBNgAaATdMAAUTAB4UAB8VACAWACEXACIAAAAAAAUTAB4UAB8VACAWACEXACIFEwAlFAAmFQAnFgAoFwApAAAAAAAFEwAlFAAmFQAnFgAoFwApAAUTAC0UAC4VAC8WADAXADEAAAAAAAUTAC0UAC4VAC8WADAXADEABRMANRQANhUANxYAOBcAOQAAAAAABRMANRQANhUANxYAOBcAOQAFEwA9FAA-FQA_FgBAFwBBAAAAAAAFEwA9FAA-FQA_FgBAFwBBAAUTAEUUAEYVAEcWAEgXAEkAAAAAAAUTAEUUAEYVAEcWAEgXAEkABRMATRQAThUATxYAUBcAUQAAAAAABRMATRQAThUATxYAUBcAUQAFEwBVFABWFQBXFgBYFwBZAAAAAAAFEwBVFABWFQBXFgBYFwBZAAUTAF0UAF4VAF8WAGAXAGEAAAAAAAUTAF0UAF4VAF8WAGAXAGEABRMAZRQAZhUAZxYAaBcAaQAAAAAABRMAZRQAZhUAZxYAaBcAaQAFEwBtFABuFQBvFgBwFwBxAAAAAAAFEwBtFABuFQBvFgBwFwBxAhMAeNYBygJ0BBMAd9IBAHPUAc4CddUB0gJ2AdMBAHQB0wEAdALUAdMCANUB1AIAAdYB1QIABRMAehQAexUAfBYAfRcAfgAAAAAABRMAehQAexUAfBYAfRcAfgUTAIEBFACCARUAgwEWAIQBFwCFAQAAAAAABRMAgQEUAIIBFQCDARYAhAEXAIUBBRMAiAEUAIkBFQCKARYAiwEXAIwBAAAAAAAFEwCIARQAiQEVAIoBFgCLARcAjAEFEwCPARQAkAEVAJEBFgCSARcAkwEAAAAAAAUTAI8BFACQARUAkQEWAJIBFwCTAQECAQIDAQUGAQYHAQcIAQkKAQoMAgsOAQwQAg8RARASARETAhgWAxkXCRoZChsaChwdCh0eCh4fCh8hCiAjAiElCiInAiMoCiQpCiUqAiYtCycuESgwEikxEio0Eis1Eiw2Ei04Ei46Ai88EjA-AjE_EjJAEjNBAjREEzVFGThHGjlNGjpQGjtRGjxSGj1UGj5WAj9YGkBaAkFbGkJcGkNdAkRgHUVhI0ZiG0djG0hkG0llG0pmG0toG0xqAk1sG05uAk9vG1BwG1FxAlJ0JFN1KlR3K1V4K1Z7K1d8K1h9K1l_K1qBAQJbgwErXIUBAl2GAStehwErX4gBAmCLASxhjAEyYo4BM2OPATNkkgEzZZMBM2aUATNnlgEzaJgBAmmaATNqnAECa50BM2yeATNtnwECbqIBNG-jATpwpQE7caYBO3KpATtzqgE7dKsBO3WtATt2rwECd7EBO3izAQJ5tAE7erUBO3u2AQJ8uQE8fboBQn68AUN_vQFDgAHAAUOBAcEBQ4IBwgFDgwHEAUOEAcYBAoUByAFDhgHKAQKHAcsBQ4gBzAFDiQHNAQKKAdABRIsB0QFKjAHTAUuNAdQBS44B1wFLjwHYAUuQAdkBS5EB2wFLkgHdAQKTAd8BS5QB4QEClQHiAUuWAeMBS5cB5AECmAHnAUyZAegBUpoB6gFTmwHrAVOcAe4BU50B7wFTngHwAVOfAfIBU6AB9AECoQH2AVOiAfgBAqMB-QFTpAH6AVOlAfsBAqYB_gFUpwH_AVqoAYECW6kBggJbqgGFAlurAYYCW6wBhwJbrQGJAluuAYsCAq8BjQJbsAGPAgKxAZACW7IBkQJbswGSAgK0AZUCXLUBlgJitgGYAmO3AZkCY7gBnAJjuQGdAmO6AZ4CY7sBoAJjvAGiAgK9AaQCY74BpgICvwGnAmPAAagCY8EBqQICwgGsAmTDAa0CasQBrwJrxQGwAmvGAbMCa8cBtAJryAG1AmvJAbcCa8oBuQICywG7AmvMAb0CAs0BvgJrzgG_AmvPAcACAtABwwJs0QHEAnLXAcYCc9gB1gJz2QHZAnPaAdoCc9sB2wJz3AHdAnPdAd8CAt4B4QJz3wHjAgLgAeQCc-EB5QJz4gHmAgLjAekCeeQB6gJ_5QHrAnTmAewCdOcB7QJ06AHuAnTpAe8CdOoB8QJ06wHzAgLsAfUCdO0B9wIC7gH4AnTvAfkCdPAB-gIC8QH9AoAB8gH-AoYB8wH_AnX0AYADdfUBgQN19gGCA3X3AYMDdfgBhQN1-QGHAwL6AYkDdfsBiwMC_AGMA3X9AY0Ddf4BjgMC_wGRA4cBgAKSA40BgQKTA3aCApQDdoMClQN2hAKWA3aFApcDdoYCmQN2hwKbAwKIAp0DdokCnwMCigKgA3aLAqEDdowCogMCjQKlA44BjgKmA5QB" + strings: JSON.parse("[\"where\",\"orderBy\",\"cursor\",\"user\",\"habit\",\"records\",\"_count\",\"habits\",\"checkIns\",\"list\",\"items\",\"checkLists\",\"templates\",\"owner\",\"project\",\"members\",\"column\",\"parent\",\"children\",\"assignee\",\"creator\",\"task\",\"tags\",\"tasks\",\"columns\",\"ownedProjects\",\"projectMembers\",\"assignedTasks\",\"createdTasks\",\"taskMembers\",\"User.findUnique\",\"User.findUniqueOrThrow\",\"User.findFirst\",\"User.findFirstOrThrow\",\"User.findMany\",\"data\",\"User.createOne\",\"User.createMany\",\"User.createManyAndReturn\",\"User.updateOne\",\"User.updateMany\",\"User.updateManyAndReturn\",\"create\",\"update\",\"User.upsertOne\",\"User.deleteOne\",\"User.deleteMany\",\"having\",\"_avg\",\"_sum\",\"_min\",\"_max\",\"User.groupBy\",\"User.aggregate\",\"Article.findUnique\",\"Article.findUniqueOrThrow\",\"Article.findFirst\",\"Article.findFirstOrThrow\",\"Article.findMany\",\"Article.createOne\",\"Article.createMany\",\"Article.createManyAndReturn\",\"Article.updateOne\",\"Article.updateMany\",\"Article.updateManyAndReturn\",\"Article.upsertOne\",\"Article.deleteOne\",\"Article.deleteMany\",\"Article.groupBy\",\"Article.aggregate\",\"Strategy.findUnique\",\"Strategy.findUniqueOrThrow\",\"Strategy.findFirst\",\"Strategy.findFirstOrThrow\",\"Strategy.findMany\",\"Strategy.createOne\",\"Strategy.createMany\",\"Strategy.createManyAndReturn\",\"Strategy.updateOne\",\"Strategy.updateMany\",\"Strategy.updateManyAndReturn\",\"Strategy.upsertOne\",\"Strategy.deleteOne\",\"Strategy.deleteMany\",\"Strategy.groupBy\",\"Strategy.aggregate\",\"indicator\",\"points\",\"Indicator.findUnique\",\"Indicator.findUniqueOrThrow\",\"Indicator.findFirst\",\"Indicator.findFirstOrThrow\",\"Indicator.findMany\",\"Indicator.createOne\",\"Indicator.createMany\",\"Indicator.createManyAndReturn\",\"Indicator.updateOne\",\"Indicator.updateMany\",\"Indicator.updateManyAndReturn\",\"Indicator.upsertOne\",\"Indicator.deleteOne\",\"Indicator.deleteMany\",\"Indicator.groupBy\",\"Indicator.aggregate\",\"IndicatorPoint.findUnique\",\"IndicatorPoint.findUniqueOrThrow\",\"IndicatorPoint.findFirst\",\"IndicatorPoint.findFirstOrThrow\",\"IndicatorPoint.findMany\",\"IndicatorPoint.createOne\",\"IndicatorPoint.createMany\",\"IndicatorPoint.createManyAndReturn\",\"IndicatorPoint.updateOne\",\"IndicatorPoint.updateMany\",\"IndicatorPoint.updateManyAndReturn\",\"IndicatorPoint.upsertOne\",\"IndicatorPoint.deleteOne\",\"IndicatorPoint.deleteMany\",\"IndicatorPoint.groupBy\",\"IndicatorPoint.aggregate\",\"News.findUnique\",\"News.findUniqueOrThrow\",\"News.findFirst\",\"News.findFirstOrThrow\",\"News.findMany\",\"News.createOne\",\"News.createMany\",\"News.createManyAndReturn\",\"News.updateOne\",\"News.updateMany\",\"News.updateManyAndReturn\",\"News.upsertOne\",\"News.deleteOne\",\"News.deleteMany\",\"News.groupBy\",\"News.aggregate\",\"Alert.findUnique\",\"Alert.findUniqueOrThrow\",\"Alert.findFirst\",\"Alert.findFirstOrThrow\",\"Alert.findMany\",\"Alert.createOne\",\"Alert.createMany\",\"Alert.createManyAndReturn\",\"Alert.updateOne\",\"Alert.updateMany\",\"Alert.updateManyAndReturn\",\"Alert.upsertOne\",\"Alert.deleteOne\",\"Alert.deleteMany\",\"Alert.groupBy\",\"Alert.aggregate\",\"ServerNode.findUnique\",\"ServerNode.findUniqueOrThrow\",\"ServerNode.findFirst\",\"ServerNode.findFirstOrThrow\",\"ServerNode.findMany\",\"ServerNode.createOne\",\"ServerNode.createMany\",\"ServerNode.createManyAndReturn\",\"ServerNode.updateOne\",\"ServerNode.updateMany\",\"ServerNode.updateManyAndReturn\",\"ServerNode.upsertOne\",\"ServerNode.deleteOne\",\"ServerNode.deleteMany\",\"ServerNode.groupBy\",\"ServerNode.aggregate\",\"ServerNodeHistory.findUnique\",\"ServerNodeHistory.findUniqueOrThrow\",\"ServerNodeHistory.findFirst\",\"ServerNodeHistory.findFirstOrThrow\",\"ServerNodeHistory.findMany\",\"ServerNodeHistory.createOne\",\"ServerNodeHistory.createMany\",\"ServerNodeHistory.createManyAndReturn\",\"ServerNodeHistory.updateOne\",\"ServerNodeHistory.updateMany\",\"ServerNodeHistory.updateManyAndReturn\",\"ServerNodeHistory.upsertOne\",\"ServerNodeHistory.deleteOne\",\"ServerNodeHistory.deleteMany\",\"ServerNodeHistory.groupBy\",\"ServerNodeHistory.aggregate\",\"ServiceCheck.findUnique\",\"ServiceCheck.findUniqueOrThrow\",\"ServiceCheck.findFirst\",\"ServiceCheck.findFirstOrThrow\",\"ServiceCheck.findMany\",\"ServiceCheck.createOne\",\"ServiceCheck.createMany\",\"ServiceCheck.createManyAndReturn\",\"ServiceCheck.updateOne\",\"ServiceCheck.updateMany\",\"ServiceCheck.updateManyAndReturn\",\"ServiceCheck.upsertOne\",\"ServiceCheck.deleteOne\",\"ServiceCheck.deleteMany\",\"ServiceCheck.groupBy\",\"ServiceCheck.aggregate\",\"Econ_SowInventory.findUnique\",\"Econ_SowInventory.findUniqueOrThrow\",\"Econ_SowInventory.findFirst\",\"Econ_SowInventory.findFirstOrThrow\",\"Econ_SowInventory.findMany\",\"Econ_SowInventory.createOne\",\"Econ_SowInventory.createMany\",\"Econ_SowInventory.createManyAndReturn\",\"Econ_SowInventory.updateOne\",\"Econ_SowInventory.updateMany\",\"Econ_SowInventory.updateManyAndReturn\",\"Econ_SowInventory.upsertOne\",\"Econ_SowInventory.deleteOne\",\"Econ_SowInventory.deleteMany\",\"Econ_SowInventory.groupBy\",\"Econ_SowInventory.aggregate\",\"ScraperCache.findUnique\",\"ScraperCache.findUniqueOrThrow\",\"ScraperCache.findFirst\",\"ScraperCache.findFirstOrThrow\",\"ScraperCache.findMany\",\"ScraperCache.createOne\",\"ScraperCache.createMany\",\"ScraperCache.createManyAndReturn\",\"ScraperCache.updateOne\",\"ScraperCache.updateMany\",\"ScraperCache.updateManyAndReturn\",\"ScraperCache.upsertOne\",\"ScraperCache.deleteOne\",\"ScraperCache.deleteMany\",\"ScraperCache.groupBy\",\"ScraperCache.aggregate\",\"OilPriceHistory.findUnique\",\"OilPriceHistory.findUniqueOrThrow\",\"OilPriceHistory.findFirst\",\"OilPriceHistory.findFirstOrThrow\",\"OilPriceHistory.findMany\",\"OilPriceHistory.createOne\",\"OilPriceHistory.createMany\",\"OilPriceHistory.createManyAndReturn\",\"OilPriceHistory.updateOne\",\"OilPriceHistory.updateMany\",\"OilPriceHistory.updateManyAndReturn\",\"OilPriceHistory.upsertOne\",\"OilPriceHistory.deleteOne\",\"OilPriceHistory.deleteMany\",\"OilPriceHistory.groupBy\",\"OilPriceHistory.aggregate\",\"MihomeAccount.findUnique\",\"MihomeAccount.findUniqueOrThrow\",\"MihomeAccount.findFirst\",\"MihomeAccount.findFirstOrThrow\",\"MihomeAccount.findMany\",\"MihomeAccount.createOne\",\"MihomeAccount.createMany\",\"MihomeAccount.createManyAndReturn\",\"MihomeAccount.updateOne\",\"MihomeAccount.updateMany\",\"MihomeAccount.updateManyAndReturn\",\"MihomeAccount.upsertOne\",\"MihomeAccount.deleteOne\",\"MihomeAccount.deleteMany\",\"MihomeAccount.groupBy\",\"MihomeAccount.aggregate\",\"home\",\"device\",\"samples\",\"hours\",\"devices\",\"MihomeHome.findUnique\",\"MihomeHome.findUniqueOrThrow\",\"MihomeHome.findFirst\",\"MihomeHome.findFirstOrThrow\",\"MihomeHome.findMany\",\"MihomeHome.createOne\",\"MihomeHome.createMany\",\"MihomeHome.createManyAndReturn\",\"MihomeHome.updateOne\",\"MihomeHome.updateMany\",\"MihomeHome.updateManyAndReturn\",\"MihomeHome.upsertOne\",\"MihomeHome.deleteOne\",\"MihomeHome.deleteMany\",\"MihomeHome.groupBy\",\"MihomeHome.aggregate\",\"MihomeDevice.findUnique\",\"MihomeDevice.findUniqueOrThrow\",\"MihomeDevice.findFirst\",\"MihomeDevice.findFirstOrThrow\",\"MihomeDevice.findMany\",\"MihomeDevice.createOne\",\"MihomeDevice.createMany\",\"MihomeDevice.createManyAndReturn\",\"MihomeDevice.updateOne\",\"MihomeDevice.updateMany\",\"MihomeDevice.updateManyAndReturn\",\"MihomeDevice.upsertOne\",\"MihomeDevice.deleteOne\",\"MihomeDevice.deleteMany\",\"MihomeDevice.groupBy\",\"MihomeDevice.aggregate\",\"MihomeEnvSample.findUnique\",\"MihomeEnvSample.findUniqueOrThrow\",\"MihomeEnvSample.findFirst\",\"MihomeEnvSample.findFirstOrThrow\",\"MihomeEnvSample.findMany\",\"MihomeEnvSample.createOne\",\"MihomeEnvSample.createMany\",\"MihomeEnvSample.createManyAndReturn\",\"MihomeEnvSample.updateOne\",\"MihomeEnvSample.updateMany\",\"MihomeEnvSample.updateManyAndReturn\",\"MihomeEnvSample.upsertOne\",\"MihomeEnvSample.deleteOne\",\"MihomeEnvSample.deleteMany\",\"MihomeEnvSample.groupBy\",\"MihomeEnvSample.aggregate\",\"MihomeEnvHour.findUnique\",\"MihomeEnvHour.findUniqueOrThrow\",\"MihomeEnvHour.findFirst\",\"MihomeEnvHour.findFirstOrThrow\",\"MihomeEnvHour.findMany\",\"MihomeEnvHour.createOne\",\"MihomeEnvHour.createMany\",\"MihomeEnvHour.createManyAndReturn\",\"MihomeEnvHour.updateOne\",\"MihomeEnvHour.updateMany\",\"MihomeEnvHour.updateManyAndReturn\",\"MihomeEnvHour.upsertOne\",\"MihomeEnvHour.deleteOne\",\"MihomeEnvHour.deleteMany\",\"MihomeEnvHour.groupBy\",\"MihomeEnvHour.aggregate\",\"Habit.findUnique\",\"Habit.findUniqueOrThrow\",\"Habit.findFirst\",\"Habit.findFirstOrThrow\",\"Habit.findMany\",\"Habit.createOne\",\"Habit.createMany\",\"Habit.createManyAndReturn\",\"Habit.updateOne\",\"Habit.updateMany\",\"Habit.updateManyAndReturn\",\"Habit.upsertOne\",\"Habit.deleteOne\",\"Habit.deleteMany\",\"Habit.groupBy\",\"Habit.aggregate\",\"CheckIn.findUnique\",\"CheckIn.findUniqueOrThrow\",\"CheckIn.findFirst\",\"CheckIn.findFirstOrThrow\",\"CheckIn.findMany\",\"CheckIn.createOne\",\"CheckIn.createMany\",\"CheckIn.createManyAndReturn\",\"CheckIn.updateOne\",\"CheckIn.updateMany\",\"CheckIn.updateManyAndReturn\",\"CheckIn.upsertOne\",\"CheckIn.deleteOne\",\"CheckIn.deleteMany\",\"CheckIn.groupBy\",\"CheckIn.aggregate\",\"CheckList.findUnique\",\"CheckList.findUniqueOrThrow\",\"CheckList.findFirst\",\"CheckList.findFirstOrThrow\",\"CheckList.findMany\",\"CheckList.createOne\",\"CheckList.createMany\",\"CheckList.createManyAndReturn\",\"CheckList.updateOne\",\"CheckList.updateMany\",\"CheckList.updateManyAndReturn\",\"CheckList.upsertOne\",\"CheckList.deleteOne\",\"CheckList.deleteMany\",\"CheckList.groupBy\",\"CheckList.aggregate\",\"CheckListItem.findUnique\",\"CheckListItem.findUniqueOrThrow\",\"CheckListItem.findFirst\",\"CheckListItem.findFirstOrThrow\",\"CheckListItem.findMany\",\"CheckListItem.createOne\",\"CheckListItem.createMany\",\"CheckListItem.createManyAndReturn\",\"CheckListItem.updateOne\",\"CheckListItem.updateMany\",\"CheckListItem.updateManyAndReturn\",\"CheckListItem.upsertOne\",\"CheckListItem.deleteOne\",\"CheckListItem.deleteMany\",\"CheckListItem.groupBy\",\"CheckListItem.aggregate\",\"CheckListTemplate.findUnique\",\"CheckListTemplate.findUniqueOrThrow\",\"CheckListTemplate.findFirst\",\"CheckListTemplate.findFirstOrThrow\",\"CheckListTemplate.findMany\",\"CheckListTemplate.createOne\",\"CheckListTemplate.createMany\",\"CheckListTemplate.createManyAndReturn\",\"CheckListTemplate.updateOne\",\"CheckListTemplate.updateMany\",\"CheckListTemplate.updateManyAndReturn\",\"CheckListTemplate.upsertOne\",\"CheckListTemplate.deleteOne\",\"CheckListTemplate.deleteMany\",\"CheckListTemplate.groupBy\",\"CheckListTemplate.aggregate\",\"Project.findUnique\",\"Project.findUniqueOrThrow\",\"Project.findFirst\",\"Project.findFirstOrThrow\",\"Project.findMany\",\"Project.createOne\",\"Project.createMany\",\"Project.createManyAndReturn\",\"Project.updateOne\",\"Project.updateMany\",\"Project.updateManyAndReturn\",\"Project.upsertOne\",\"Project.deleteOne\",\"Project.deleteMany\",\"Project.groupBy\",\"Project.aggregate\",\"ProjectMember.findUnique\",\"ProjectMember.findUniqueOrThrow\",\"ProjectMember.findFirst\",\"ProjectMember.findFirstOrThrow\",\"ProjectMember.findMany\",\"ProjectMember.createOne\",\"ProjectMember.createMany\",\"ProjectMember.createManyAndReturn\",\"ProjectMember.updateOne\",\"ProjectMember.updateMany\",\"ProjectMember.updateManyAndReturn\",\"ProjectMember.upsertOne\",\"ProjectMember.deleteOne\",\"ProjectMember.deleteMany\",\"ProjectMember.groupBy\",\"ProjectMember.aggregate\",\"BoardColumn.findUnique\",\"BoardColumn.findUniqueOrThrow\",\"BoardColumn.findFirst\",\"BoardColumn.findFirstOrThrow\",\"BoardColumn.findMany\",\"BoardColumn.createOne\",\"BoardColumn.createMany\",\"BoardColumn.createManyAndReturn\",\"BoardColumn.updateOne\",\"BoardColumn.updateMany\",\"BoardColumn.updateManyAndReturn\",\"BoardColumn.upsertOne\",\"BoardColumn.deleteOne\",\"BoardColumn.deleteMany\",\"BoardColumn.groupBy\",\"BoardColumn.aggregate\",\"Task.findUnique\",\"Task.findUniqueOrThrow\",\"Task.findFirst\",\"Task.findFirstOrThrow\",\"Task.findMany\",\"Task.createOne\",\"Task.createMany\",\"Task.createManyAndReturn\",\"Task.updateOne\",\"Task.updateMany\",\"Task.updateManyAndReturn\",\"Task.upsertOne\",\"Task.deleteOne\",\"Task.deleteMany\",\"Task.groupBy\",\"Task.aggregate\",\"TaskMember.findUnique\",\"TaskMember.findUniqueOrThrow\",\"TaskMember.findFirst\",\"TaskMember.findFirstOrThrow\",\"TaskMember.findMany\",\"TaskMember.createOne\",\"TaskMember.createMany\",\"TaskMember.createManyAndReturn\",\"TaskMember.updateOne\",\"TaskMember.updateMany\",\"TaskMember.updateManyAndReturn\",\"TaskMember.upsertOne\",\"TaskMember.deleteOne\",\"TaskMember.deleteMany\",\"TaskMember.groupBy\",\"TaskMember.aggregate\",\"TaskTag.findUnique\",\"TaskTag.findUniqueOrThrow\",\"TaskTag.findFirst\",\"TaskTag.findFirstOrThrow\",\"TaskTag.findMany\",\"TaskTag.createOne\",\"TaskTag.createMany\",\"TaskTag.createManyAndReturn\",\"TaskTag.updateOne\",\"TaskTag.updateMany\",\"TaskTag.updateManyAndReturn\",\"TaskTag.upsertOne\",\"TaskTag.deleteOne\",\"TaskTag.deleteMany\",\"TaskTag.groupBy\",\"TaskTag.aggregate\",\"AND\",\"OR\",\"NOT\",\"id\",\"uuid\",\"taskUuid\",\"taskId\",\"name\",\"equals\",\"in\",\"notIn\",\"lt\",\"lte\",\"gt\",\"gte\",\"contains\",\"startsWith\",\"endsWith\",\"not\",\"userId\",\"projectUuid\",\"columnUuid\",\"parentUuid\",\"projectId\",\"columnId\",\"parentId\",\"title\",\"description\",\"kind\",\"markDate\",\"status\",\"doneAt\",\"assigneeId\",\"creatorId\",\"extras\",\"sortOrder\",\"archived\",\"archivedAt\",\"archiveSource\",\"createdAt\",\"updatedAt\",\"number\",\"string_contains\",\"string_starts_with\",\"string_ends_with\",\"array_starts_with\",\"array_ends_with\",\"array_contains\",\"boundStatus\",\"retention\",\"permission\",\"code\",\"ownerId\",\"isPersonal\",\"notes\",\"taskKinds\",\"icon\",\"autoArchive\",\"scene\",\"builtin\",\"listId\",\"note\",\"done\",\"habitId\",\"date\",\"seq\",\"source\",\"period\",\"target\",\"color\",\"deviceId\",\"hour\",\"temperatureAvg\",\"humidityAvg\",\"raw\",\"temperature\",\"humidity\",\"temperatureMin\",\"temperatureMax\",\"humidityMin\",\"humidityMax\",\"origin\",\"sampledAt\",\"did\",\"homeId\",\"model\",\"alias\",\"tempSiid\",\"tempPiid\",\"humSiid\",\"humPiid\",\"battSiid\",\"battPiid\",\"battery\",\"batteryAt\",\"enabled\",\"lastSyncAt\",\"every\",\"some\",\"none\",\"deviceId_hour\",\"deviceId_date\",\"key\",\"lastError\",\"province\",\"fuel\",\"price\",\"date_province_fuel\",\"scraperName\",\"lastDataHash\",\"lastUpdateAt\",\"metadata\",\"month\",\"inventory\",\"url\",\"latencyMs\",\"message\",\"lastCheckAt\",\"cpu\",\"ram\",\"load\",\"disk\",\"netIn\",\"netOut\",\"process\",\"connections\",\"uuid_period\",\"cpuName\",\"os\",\"region\",\"memTotal\",\"diskTotal\",\"cpuCores\",\"cpuUsage\",\"ramUsed\",\"swapUsed\",\"load1\",\"load5\",\"load15\",\"diskUsed\",\"uptime\",\"recordedAt\",\"indicatorName\",\"valueA\",\"valueB\",\"sourceA\",\"sourceB\",\"diff\",\"diffRatio\",\"summary\",\"content\",\"publishedAt\",\"impactRank\",\"title_publishedAt\",\"indicatorId\",\"value\",\"groupName\",\"unit\",\"frequency\",\"hidden\",\"indicatorId_period\",\"slug\",\"email\",\"password\",\"taskId_userId\",\"projectId_number\",\"projectId_name\",\"projectId_userId\",\"habitId_date_seq\",\"is\",\"isNot\",\"connectOrCreate\",\"upsert\",\"createMany\",\"set\",\"disconnect\",\"delete\",\"connect\",\"updateMany\",\"deleteMany\",\"increment\",\"decrement\",\"multiply\",\"divide\"]"), + graph: "mgysAtADEgcAAIoHACAIAACLBwAgCwAAjAcAIAwAAI0HACAZAACOBwAgGgAAhwcAIBsAAP4GACAcAAD-BgAgHQAAgAcAIP0DAACJBwAw_gMAABoAEP8DAACJBwAwgAQCAAAAAYQEAQC5BgAhpARAALoGACGlBEAAugYAIZ8FAQAAAAGgBQEAuQYAIQEAAAABACARAwAA-AYAIAUAAIsHACD9AwAAlwcAMP4DAAADABD_AwAAlwcAMIAEAgC4BgAhhAQBALkGACGQBAIAuAYAIaAEAgC4BgAhoQQgAMcGACGkBEAAugYAIaUEQAC6BgAhtQQBALkGACG6BAEAuQYAIcAEAQC5BgAhwQQCALgGACHCBAEAuQYAIQIDAACPCwAgBQAAhwsAIBEDAAD4BgAgBQAAiwcAIP0DAACXBwAw_gMAAAMAEP8DAACXBwAwgAQCAAAAAYQEAQC5BgAhkAQCALgGACGgBAIAuAYAIaEEIADHBgAhpARAALoGACGlBEAAugYAIbUEAQC5BgAhugQBALkGACHABAEAuQYAIcEEAgC4BgAhwgQBALkGACEDAAAAAwAgAQAABAAwAgAABQAgDQMAAPgGACAEAACWBwAg_QMAAJUHADD-AwAABwAQ_wMAAJUHADCABAIAuAYAIZAEAgC4BgAhpARAALoGACG6BAEAuQYAIbwEAgC4BgAhvQQBALkGACG-BAIAuAYAIb8EAQC5BgAhAgMAAI8LACAEAACWCwAgDgMAAPgGACAEAACWBwAg_QMAAJUHADD-AwAABwAQ_wMAAJUHADCABAIAAAABkAQCALgGACGkBEAAugYAIboEAQC5BgAhvAQCALgGACG9BAEAuQYAIb4EAgC4BgAhvwQBALkGACGlBQAAlAcAIAMAAAAHACABAAAIADACAAAJACABAAAABwAgAwAAAAcAIAEAAAgAMAIAAAkAIA0DAAD4BgAgCgAAkwcAIP0DAACSBwAw_gMAAA0AEP8DAACSBwAwgAQCALgGACGQBAIAuAYAIZcEAQC5BgAhoQQgAMcGACGkBEAAugYAIaUEQAC6BgAhtwQBALkGACG6BAEAuQYAIQIDAACPCwAgCgAAlQsAIA0DAAD4BgAgCgAAkwcAIP0DAACSBwAw_gMAAA0AEP8DAACSBwAwgAQCAAAAAZAEAgC4BgAhlwQBALkGACGhBCAAxwYAIaQEQAC6BgAhpQRAALoGACG3BAEAuQYAIboEAQC5BgAhAwAAAA0AIAEAAA4AMAIAAA8AIA0JAACRBwAg_QMAAJAHADD-AwAAEQAQ_wMAAJAHADCABAIAuAYAIZcEAQC5BgAhnARAAMMGACGgBAIAuAYAIaQEQAC6BgAhpQRAALoGACG5BAIAuAYAIboEAQC5BgAhuwQgAMcGACECCQAAlAsAIJwEAACYBwAgDQkAAJEHACD9AwAAkAcAMP4DAAARABD_AwAAkAcAMIAEAgAAAAGXBAEAuQYAIZwEQADDBgAhoAQCALgGACGkBEAAugYAIaUEQAC6BgAhuQQCALgGACG6BAEAuQYAIbsEIADHBgAhAwAAABEAIAEAABIAMAIAABMAIAEAAAARACAMAwAA_wYAIAoAAPAGACD9AwAAjwcAMP4DAAAWABD_AwAAjwcAMIAEAgC4BgAhhAQBALkGACGQBAIAxgYAIaQEQAC6BgAhpQRAALoGACG3BAEAuQYAIbgEIADHBgAhAgMAAI8LACCQBAAAmAcAIAwDAAD_BgAgCgAA8AYAIP0DAACPBwAw_gMAABYAEP8DAACPBwAwgAQCAAAAAYQEAQC5BgAhkAQCAMYGACGkBEAAugYAIaUEQAC6BgAhtwQBALkGACG4BCAAxwYAIQMAAAAWACABAAAXADACAAAYACASBwAAigcAIAgAAIsHACALAACMBwAgDAAAjQcAIBkAAI4HACAaAACHBwAgGwAA_gYAIBwAAP4GACAdAACABwAg_QMAAIkHADD-AwAAGgAQ_wMAAIkHADCABAIAuAYAIYQEAQC5BgAhpARAALoGACGlBEAAugYAIZ8FAQC5BgAhoAUBALkGACEBAAAAGgAgFA0AAPgGACAPAACHBwAgFwAA_gYAIBgAAIgHACD9AwAAhgcAMP4DAAAcABD_AwAAhgcAMIAEAgC4BgAhgQQBALkGACGEBAEAuQYAIaEEIADHBgAhpARAALoGACGlBEAAugYAIbAEAQC5BgAhsQQCALgGACGyBCAAxwYAIbMEAADwBgAgtAQAAL8GACC1BAEAxQYAIbYEAQC5BgAhBg0AAI8LACAPAACLCwAgFwAAjAsAIBgAAJMLACC0BAAAmAcAILUEAACYBwAgFA0AAPgGACAPAACHBwAgFwAA_gYAIBgAAIgHACD9AwAAhgcAMP4DAAAcABD_AwAAhgcAMIAEAgAAAAGBBAEAAAABhAQBALkGACGhBCAAxwYAIaQEQAC6BgAhpQRAALoGACGwBAEAAAABsQQCALgGACGyBCAAxwYAIbMEAADwBgAgtAQAAL8GACC1BAEAxQYAIbYEAQC5BgAhAwAAABwAIAEAAB0AMAIAAB4AIAwDAAD4BgAgDgAA-wYAIP0DAACFBwAw_gMAACAAEP8DAACFBwAwgAQCALgGACGBBAEAuQYAIZAEAgC4BgAhkQQBAMUGACGUBAIAuAYAIaQEQAC6BgAhrwQBALkGACEDAwAAjwsAIA4AAJALACCRBAAAmAcAIA0DAAD4BgAgDgAA-wYAIP0DAACFBwAw_gMAACAAEP8DAACFBwAwgAQCAAAAAYEEAQAAAAGQBAIAuAYAIZEEAQDFBgAhlAQCALgGACGkBEAAugYAIa8EAQC5BgAhpAUAAIQHACADAAAAIAAgAQAAIQAwAgAAIgAgDw4AAPsGACAXAAD-BgAg_QMAAIMHADD-AwAAJAAQ_wMAAIMHADCABAIAuAYAIYEEAQC5BgAhhAQBALkGACGRBAEAxQYAIZQEAgC4BgAhoAQCALgGACGkBEAAugYAIaUEQAC6BgAhrQQBAMUGACGuBAEAuQYAIQQOAACQCwAgFwAAjAsAIJEEAACYBwAgrQQAAJgHACAQDgAA-wYAIBcAAP4GACD9AwAAgwcAMP4DAAAkABD_AwAAgwcAMIAEAgAAAAGBBAEAAAABhAQBALkGACGRBAEAxQYAIZQEAgC4BgAhoAQCALgGACGkBEAAugYAIaUEQAC6BgAhrQQBAMUGACGuBAEAuQYAIaMFAACCBwAgAwAAACQAIAEAACUAMAIAACYAICMOAAD7BgAgDwAAgAcAIBAAAPwGACARAAD9BgAgEgAA_gYAIBMAAP8GACAUAAD4BgAgFgAAgQcAIP0DAAD6BgAw_gMAACgAEP8DAAD6BgAwgAQCALgGACGBBAEAuQYAIZEEAQDFBgAhkgQBAMUGACGTBAEAxQYAIZQEAgC4BgAhlQQCALgGACGWBAIAxgYAIZcEAQC5BgAhmAQBALkGACGZBAEAxQYAIZoEQADDBgAhmwQBALkGACGcBEAAwwYAIZ0EAgDGBgAhngQCALgGACGfBAAA8AYAIKAEAgC4BgAhoQQgAMcGACGiBEAAwwYAIaMEAQDFBgAhpARAALoGACGlBEAAugYAIaYEAgDGBgAhEw4AAJALACAPAACNCwAgEAAAkQsAIBEAAI4LACASAACMCwAgEwAAjwsAIBQAAI8LACAWAACSCwAgkQQAAJgHACCSBAAAmAcAIJMEAACYBwAglgQAAJgHACCZBAAAmAcAIJoEAACYBwAgnAQAAJgHACCdBAAAmAcAIKIEAACYBwAgowQAAJgHACCmBAAAmAcAICQOAAD7BgAgDwAAgAcAIBAAAPwGACARAAD9BgAgEgAA_gYAIBMAAP8GACAUAAD4BgAgFgAAgQcAIP0DAAD6BgAw_gMAACgAEP8DAAD6BgAwgAQCAAAAAYEEAQAAAAGRBAEAxQYAIZIEAQDFBgAhkwQBAMUGACGUBAIAuAYAIZUEAgC4BgAhlgQCAMYGACGXBAEAuQYAIZgEAQC5BgAhmQQBAMUGACGaBEAAwwYAIZsEAQC5BgAhnARAAMMGACGdBAIAxgYAIZ4EAgC4BgAhnwQAAPAGACCgBAIAuAYAIaEEIADHBgAhogRAAMMGACGjBAEAxQYAIaQEQAC6BgAhpQRAALoGACGmBAIAxgYAIaIFAAD5BgAgAwAAACgAIAEAACkAMAIAACoAIAEAAAAoACADAAAAKAAgAQAAKQAwAgAAKgAgAQAAABoAIAoDAAD4BgAgFQAA9QYAIP0DAAD3BgAw_gMAAC8AEP8DAAD3BgAwgAQCALgGACGBBAEAuQYAIYIEAQDFBgAhgwQCALgGACGQBAIAuAYAIQMDAACPCwAgFQAAjgsAIIIEAACYBwAgCwMAAPgGACAVAAD1BgAg_QMAAPcGADD-AwAALwAQ_wMAAPcGADCABAIAAAABgQQBAAAAAYIEAQDFBgAhgwQCALgGACGQBAIAuAYAIaEFAAD2BgAgAwAAAC8AIAEAADAAMAIAADEAIAkVAAD1BgAg_QMAAPQGADD-AwAAMwAQ_wMAAPQGADCABAIAuAYAIYEEAQC5BgAhggQBAMUGACGDBAIAuAYAIYQEAQC5BgAhAhUAAI4LACCCBAAAmAcAIAkVAAD1BgAg_QMAAPQGADD-AwAAMwAQ_wMAAPQGADCABAIAAAABgQQBAAAAAYIEAQDFBgAhgwQCALgGACGEBAEAuQYAIQMAAAAzACABAAA0ADACAAA1ACABAAAAKAAgAQAAAC8AIAEAAAAzACABAAAAKAAgAwAAACgAIAEAACkAMAIAACoAIAEAAAAgACABAAAAJAAgAQAAACgAIAMAAAAgACABAAAhADACAAAiACADAAAAKAAgAQAAKQAwAgAAKgAgAwAAACgAIAEAACkAMAIAACoAIAMAAAAvACABAAAwADACAAAxACABAAAAAwAgAQAAAAcAIAEAAAANACABAAAAFgAgAQAAABwAIAEAAAAgACABAAAAKAAgAQAAACgAIAEAAAAvACABAAAAAQAgCQcAAIYLACAIAACHCwAgCwAAiAsAIAwAAIkLACAZAACKCwAgGgAAiwsAIBsAAIwLACAcAACMCwAgHQAAjQsAIAMAAAAaACABAABNADACAAABACADAAAAGgAgAQAATQAwAgAAAQAgAwAAABoAIAEAAE0AMAIAAAEAIA8HAAD9CgAgCAAA_goAIAsAAP8KACAMAACACwAgGQAAgQsAIBoAAIILACAbAACDCwAgHAAAhAsAIB0AAIULACCABAIAAAABhAQBAAAAAaQEQAAAAAGlBEAAAAABnwUBAAAAAaAFAQAAAAEBIwAAUQAgBoAEAgAAAAGEBAEAAAABpARAAAAAAaUEQAAAAAGfBQEAAAABoAUBAAAAAQEjAABTADABIwAAUwAwDwcAAJcKACAIAACYCgAgCwAAmQoAIAwAAJoKACAZAACbCgAgGgAAnAoAIBsAAJ0KACAcAACeCgAgHQAAnwoAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhnwUBAJ4HACGgBQEAngcAIQIAAAABACAjAABWACAGgAQCAKAHACGEBAEAngcAIaQEQACzBwAhpQRAALMHACGfBQEAngcAIaAFAQCeBwAhAgAAABoAICMAAFgAIAIAAAAaACAjAABYACADAAAAAQAgKgAAUQAgKwAAVgAgAQAAAAEAIAEAAAAaACAFBgAAkgoAIDAAAJMKACAxAACWCgAgMgAAlQoAIDMAAJQKACAJ_QMAAPMGADD-AwAAXwAQ_wMAAPMGADCABAIAjAYAIYQEAQCNBgAhpARAAJ0GACGlBEAAnQYAIZ8FAQCNBgAhoAUBAI0GACEDAAAAGgAgAQAAXgAwLwAAXwAgAwAAABoAIAEAAE0AMAIAAAEAIAr9AwAA8gYAMP4DAABlABD_AwAA8gYAMIAEAgAAAAGXBAEAuQYAIaQEQAC6BgAhpQRAALoGACG9BAEAuQYAIZMFAQC5BgAhngUBAAAAAQEAAABiACABAAAAYgAgCv0DAADyBgAw_gMAAGUAEP8DAADyBgAwgAQCALgGACGXBAEAuQYAIaQEQAC6BgAhpQRAALoGACG9BAEAuQYAIZMFAQC5BgAhngUBALkGACEAAwAAAGUAIAEAAGYAMAIAAGIAIAMAAABlACABAABmADACAABiACADAAAAZQAgAQAAZgAwAgAAYgAgB4AEAgAAAAGXBAEAAAABpARAAAAAAaUEQAAAAAG9BAEAAAABkwUBAAAAAZ4FAQAAAAEBIwAAagAgB4AEAgAAAAGXBAEAAAABpARAAAAAAaUEQAAAAAG9BAEAAAABkwUBAAAAAZ4FAQAAAAEBIwAAbAAwASMAAGwAMAeABAIAoAcAIZcEAQCeBwAhpARAALMHACGlBEAAswcAIb0EAQCeBwAhkwUBAJ4HACGeBQEAngcAIQIAAABiACAjAABvACAHgAQCAKAHACGXBAEAngcAIaQEQACzBwAhpQRAALMHACG9BAEAngcAIZMFAQCeBwAhngUBAJ4HACECAAAAZQAgIwAAcQAgAgAAAGUAICMAAHEAIAMAAABiACAqAABqACArAABvACABAAAAYgAgAQAAAGUAIAUGAACNCgAgMAAAjgoAIDEAAJEKACAyAACQCgAgMwAAjwoAIAr9AwAA8QYAMP4DAAB4ABD_AwAA8QYAMIAEAgCMBgAhlwQBAI0GACGkBEAAnQYAIaUEQACdBgAhvQQBAI0GACGTBQEAjQYAIZ4FAQCNBgAhAwAAAGUAIAEAAHcAMC8AAHgAIAMAAABlACABAABmADACAABiACAJIwAA8AYAIP0DAADvBgAw_gMAAH4AEP8DAADvBgAwgAQCAAAAAYQEAQC5BgAhmAQBALkGACGkBEAAugYAIaUEQAC6BgAhAQAAAHsAIAEAAAB7ACAJIwAA8AYAIP0DAADvBgAw_gMAAH4AEP8DAADvBgAwgAQCALgGACGEBAEAuQYAIZgEAQC5BgAhpARAALoGACGlBEAAugYAIQADAAAAfgAgAQAAfwAwAgAAewAgAwAAAH4AIAEAAH8AMAIAAHsAIAMAAAB-ACABAAB_ADACAAB7ACAGI4AAAAABgAQCAAAAAYQEAQAAAAGYBAEAAAABpARAAAAAAaUEQAAAAAEBIwAAgwEAIAYjgAAAAAGABAIAAAABhAQBAAAAAZgEAQAAAAGkBEAAAAABpQRAAAAAAQEjAACFAQAwASMAAIUBADAGI4AAAAABgAQCAKAHACGEBAEAngcAIZgEAQCeBwAhpARAALMHACGlBEAAswcAIQIAAAB7ACAjAACIAQAgBiOAAAAAAYAEAgCgBwAhhAQBAJ4HACGYBAEAngcAIaQEQACzBwAhpQRAALMHACECAAAAfgAgIwAAigEAIAIAAAB-ACAjAACKAQAgAwAAAHsAICoAAIMBACArAACIAQAgAQAAAHsAIAEAAAB-ACAFBgAAiAoAIDAAAIkKACAxAACMCgAgMgAAiwoAIDMAAIoKACAJIwAAmwYAIP0DAADuBgAw_gMAAJEBABD_AwAA7gYAMIAEAgCMBgAhhAQBAI0GACGYBAEAjQYAIaQEQACdBgAhpQRAAJ0GACEDAAAAfgAgAQAAkAEAMC8AAJEBACADAAAAfgAgAQAAfwAwAgAAewAgDlcAAOoGACD9AwAA6QYAMP4DAACcAQAQ_wMAAOkGADCABAIAAAABhAQBAAAAAZcEAQC5BgAhpARAALoGACGlBEAAugYAIb8EAQC5BgAhmQUBALkGACGaBQEAuQYAIZsFAQC5BgAhnAUgAMcGACEBAAAAlAEAIApWAADtBgAg_QMAAOwGADD-AwAAlgEAEP8DAADsBgAwgAQCALgGACGkBEAAugYAIaUEQAC6BgAhwAQBALkGACGXBQIAuAYAIZgFCADRBgAhAVYAAIcKACALVgAA7QYAIP0DAADsBgAw_gMAAJYBABD_AwAA7AYAMIAEAgAAAAGkBEAAugYAIaUEQAC6BgAhwAQBALkGACGXBQIAuAYAIZgFCADRBgAhnQUAAOsGACADAAAAlgEAIAEAAJcBADACAACYAQAgAQAAAJYBACABAAAAlAEAIA5XAADqBgAg_QMAAOkGADD-AwAAnAEAEP8DAADpBgAwgAQCALgGACGEBAEAuQYAIZcEAQC5BgAhpARAALoGACGlBEAAugYAIb8EAQC5BgAhmQUBALkGACGaBQEAuQYAIZsFAQC5BgAhnAUgAMcGACEBVwAAhgoAIAMAAACcAQAgAQAAnQEAMAIAAJQBACADAAAAnAEAIAEAAJ0BADACAACUAQAgAwAAAJwBACABAACdAQAwAgAAlAEAIAtXAACFCgAggAQCAAAAAYQEAQAAAAGXBAEAAAABpARAAAAAAaUEQAAAAAG_BAEAAAABmQUBAAAAAZoFAQAAAAGbBQEAAAABnAUgAAAAAQEjAAChAQAgCoAEAgAAAAGEBAEAAAABlwQBAAAAAaQEQAAAAAGlBEAAAAABvwQBAAAAAZkFAQAAAAGaBQEAAAABmwUBAAAAAZwFIAAAAAEBIwAAowEAMAEjAACjAQAwC1cAAPgJACCABAIAoAcAIYQEAQCeBwAhlwQBAJ4HACGkBEAAswcAIaUEQACzBwAhvwQBAJ4HACGZBQEAngcAIZoFAQCeBwAhmwUBAJ4HACGcBSAAsgcAIQIAAACUAQAgIwAApgEAIAqABAIAoAcAIYQEAQCeBwAhlwQBAJ4HACGkBEAAswcAIaUEQACzBwAhvwQBAJ4HACGZBQEAngcAIZoFAQCeBwAhmwUBAJ4HACGcBSAAsgcAIQIAAACcAQAgIwAAqAEAIAIAAACcAQAgIwAAqAEAIAMAAACUAQAgKgAAoQEAICsAAKYBACABAAAAlAEAIAEAAACcAQAgBQYAAPMJACAwAAD0CQAgMQAA9wkAIDIAAPYJACAzAAD1CQAgDf0DAADoBgAw_gMAAK8BABD_AwAA6AYAMIAEAgCMBgAhhAQBAI0GACGXBAEAjQYAIaQEQACdBgAhpQRAAJ0GACG_BAEAjQYAIZkFAQCNBgAhmgUBAI0GACGbBQEAjQYAIZwFIACcBgAhAwAAAJwBACABAACuAQAwLwAArwEAIAMAAACcAQAgAQAAnQEAMAIAAJQBACABAAAAmAEAIAEAAACYAQAgAwAAAJYBACABAACXAQAwAgAAmAEAIAMAAACWAQAgAQAAlwEAMAIAAJgBACADAAAAlgEAIAEAAJcBADACAACYAQAgB1YAAPIJACCABAIAAAABpARAAAAAAaUEQAAAAAHABAEAAAABlwUCAAAAAZgFCAAAAAEBIwAAtwEAIAaABAIAAAABpARAAAAAAaUEQAAAAAHABAEAAAABlwUCAAAAAZgFCAAAAAEBIwAAuQEAMAEjAAC5AQAwB1YAAPEJACCABAIAoAcAIaQEQACzBwAhpQRAALMHACHABAEAngcAIZcFAgCgBwAhmAUIAMcJACECAAAAmAEAICMAALwBACAGgAQCAKAHACGkBEAAswcAIaUEQACzBwAhwAQBAJ4HACGXBQIAoAcAIZgFCADHCQAhAgAAAJYBACAjAAC-AQAgAgAAAJYBACAjAAC-AQAgAwAAAJgBACAqAAC3AQAgKwAAvAEAIAEAAACYAQAgAQAAAJYBACAFBgAA7AkAIDAAAO0JACAxAADwCQAgMgAA7wkAIDMAAO4JACAJ_QMAAOcGADD-AwAAxQEAEP8DAADnBgAwgAQCAIwGACGkBEAAnQYAIaUEQACdBgAhwAQBAI0GACGXBQIAjAYAIZgFCADOBgAhAwAAAJYBACABAADEAQAwLwAAxQEAIAMAAACWAQAgAQAAlwEAMAIAAJgBACAN_QMAAOUGADD-AwAAywEAEP8DAADlBgAwgAQCAAAAAZcEAQC5BgAhpARAALoGACG_BAEAuQYAIe8EAQC5BgAhkgUBALkGACGTBQEAxQYAIZQFQAC6BgAhlQUCALgGACGWBQAA5gYAIAEAAADIAQAgAQAAAMgBACAM_QMAAOUGADD-AwAAywEAEP8DAADlBgAwgAQCALgGACGXBAEAuQYAIaQEQAC6BgAhvwQBALkGACHvBAEAuQYAIZIFAQC5BgAhkwUBAMUGACGUBUAAugYAIZUFAgC4BgAhAZMFAACYBwAgAwAAAMsBACABAADMAQAwAgAAyAEAIAMAAADLAQAgAQAAzAEAMAIAAMgBACADAAAAywEAIAEAAMwBADACAADIAQAgCYAEAgAAAAGXBAEAAAABpARAAAAAAb8EAQAAAAHvBAEAAAABkgUBAAAAAZMFAQAAAAGUBUAAAAABlQUCAAAAAQEjAADQAQAgCYAEAgAAAAGXBAEAAAABpARAAAAAAb8EAQAAAAHvBAEAAAABkgUBAAAAAZMFAQAAAAGUBUAAAAABlQUCAAAAAQEjAADSAQAwASMAANIBADAJgAQCAKAHACGXBAEAngcAIaQEQACzBwAhvwQBAJ4HACHvBAEAngcAIZIFAQCeBwAhkwUBAJ8HACGUBUAAswcAIZUFAgCgBwAhAgAAAMgBACAjAADVAQAgCYAEAgCgBwAhlwQBAJ4HACGkBEAAswcAIb8EAQCeBwAh7wQBAJ4HACGSBQEAngcAIZMFAQCfBwAhlAVAALMHACGVBQIAoAcAIQIAAADLAQAgIwAA1wEAIAIAAADLAQAgIwAA1wEAIAMAAADIAQAgKgAA0AEAICsAANUBACABAAAAyAEAIAEAAADLAQAgBgYAAOcJACAwAADoCQAgMQAA6wkAIDIAAOoJACAzAADpCQAgkwUAAJgHACAM_QMAAOQGADD-AwAA3gEAEP8DAADkBgAwgAQCAIwGACGXBAEAjQYAIaQEQACdBgAhvwQBAI0GACHvBAEAjQYAIZIFAQCNBgAhkwUBAI4GACGUBUAAnQYAIZUFAgCMBgAhAwAAAMsBACABAADdAQAwLwAA3gEAIAMAAADLAQAgAQAAzAEAMAIAAMgBACAN_QMAAOMGADD-AwAA5AEAEP8DAADjBgAwgAQCAAAAAaQEQAC6BgAhwAQBALkGACGLBQEAuQYAIYwFCADRBgAhjQUIANEGACGOBQEAuQYAIY8FAQC5BgAhkAUIANEGACGRBQgA0QYAIQEAAADhAQAgAQAAAOEBACAN_QMAAOMGADD-AwAA5AEAEP8DAADjBgAwgAQCALgGACGkBEAAugYAIcAEAQC5BgAhiwUBALkGACGMBQgA0QYAIY0FCADRBgAhjgUBALkGACGPBQEAuQYAIZAFCADRBgAhkQUIANEGACEAAwAAAOQBACABAADlAQAwAgAA4QEAIAMAAADkAQAgAQAA5QEAMAIAAOEBACADAAAA5AEAIAEAAOUBADACAADhAQAgCoAEAgAAAAGkBEAAAAABwAQBAAAAAYsFAQAAAAGMBQgAAAABjQUIAAAAAY4FAQAAAAGPBQEAAAABkAUIAAAAAZEFCAAAAAEBIwAA6QEAIAqABAIAAAABpARAAAAAAcAEAQAAAAGLBQEAAAABjAUIAAAAAY0FCAAAAAGOBQEAAAABjwUBAAAAAZAFCAAAAAGRBQgAAAABASMAAOsBADABIwAA6wEAMAqABAIAoAcAIaQEQACzBwAhwAQBAJ4HACGLBQEAngcAIYwFCADHCQAhjQUIAMcJACGOBQEAngcAIY8FAQCeBwAhkAUIAMcJACGRBQgAxwkAIQIAAADhAQAgIwAA7gEAIAqABAIAoAcAIaQEQACzBwAhwAQBAJ4HACGLBQEAngcAIYwFCADHCQAhjQUIAMcJACGOBQEAngcAIY8FAQCeBwAhkAUIAMcJACGRBQgAxwkAIQIAAADkAQAgIwAA8AEAIAIAAADkAQAgIwAA8AEAIAMAAADhAQAgKgAA6QEAICsAAO4BACABAAAA4QEAIAEAAADkAQAgBQYAAOIJACAwAADjCQAgMQAA5gkAIDIAAOUJACAzAADkCQAgDf0DAADiBgAw_gMAAPcBABD_AwAA4gYAMIAEAgCMBgAhpARAAJ0GACHABAEAjQYAIYsFAQCNBgAhjAUIAM4GACGNBQgAzgYAIY4FAQCNBgAhjwUBAI0GACGQBQgAzgYAIZEFCADOBgAhAwAAAOQBACABAAD2AQAwLwAA9wEAIAMAAADkAQAgAQAA5QEAMAIAAOEBACAc_QMAAOEGADD-AwAA_QEAEP8DAADhBgAwgAQCAAAAAYEEAQAAAAGEBAEAuQYAIZsEAQC5BgAhpARAALoGACGlBEAAugYAIfcEBADeBgAh-AQEAN4GACH5BAIAuAYAIfoEAgC4BgAh_AQBALkGACH9BAEAuQYAIf4EAQC5BgAh_wQEAN4GACGABQQA3gYAIYEFAgC4BgAhggUIANEGACGDBQQA3gYAIYQFBADeBgAhhQUIANEGACGGBQgA0QYAIYcFCADRBgAhiAUEAN4GACGJBQQA3gYAIYoFQAC6BgAhAQAAAPoBACABAAAA-gEAIBz9AwAA4QYAMP4DAAD9AQAQ_wMAAOEGADCABAIAuAYAIYEEAQC5BgAhhAQBALkGACGbBAEAuQYAIaQEQAC6BgAhpQRAALoGACH3BAQA3gYAIfgEBADeBgAh-QQCALgGACH6BAIAuAYAIfwEAQC5BgAh_QQBALkGACH-BAEAuQYAIf8EBADeBgAhgAUEAN4GACGBBQIAuAYAIYIFCADRBgAhgwUEAN4GACGEBQQA3gYAIYUFCADRBgAhhgUIANEGACGHBQgA0QYAIYgFBADeBgAhiQUEAN4GACGKBUAAugYAIQADAAAA_QEAIAEAAP4BADACAAD6AQAgAwAAAP0BACABAAD-AQAwAgAA-gEAIAMAAAD9AQAgAQAA_gEAMAIAAPoBACAZgAQCAAAAAYEEAQAAAAGEBAEAAAABmwQBAAAAAaQEQAAAAAGlBEAAAAAB9wQEAAAAAfgEBAAAAAH5BAIAAAAB-gQCAAAAAfwEAQAAAAH9BAEAAAAB_gQBAAAAAf8EBAAAAAGABQQAAAABgQUCAAAAAYIFCAAAAAGDBQQAAAABhAUEAAAAAYUFCAAAAAGGBQgAAAABhwUIAAAAAYgFBAAAAAGJBQQAAAABigVAAAAAAQEjAACCAgAgGYAEAgAAAAGBBAEAAAABhAQBAAAAAZsEAQAAAAGkBEAAAAABpQRAAAAAAfcEBAAAAAH4BAQAAAAB-QQCAAAAAfoEAgAAAAH8BAEAAAAB_QQBAAAAAf4EAQAAAAH_BAQAAAABgAUEAAAAAYEFAgAAAAGCBQgAAAABgwUEAAAAAYQFBAAAAAGFBQgAAAABhgUIAAAAAYcFCAAAAAGIBQQAAAABiQUEAAAAAYoFQAAAAAEBIwAAhAIAMAEjAACEAgAwGYAEAgCgBwAhgQQBAJ4HACGEBAEAngcAIZsEAQCeBwAhpARAALMHACGlBEAAswcAIfcEBADcCQAh-AQEANwJACH5BAIAoAcAIfoEAgCgBwAh_AQBAJ4HACH9BAEAngcAIf4EAQCeBwAh_wQEANwJACGABQQA3AkAIYEFAgCgBwAhggUIAMcJACGDBQQA3AkAIYQFBADcCQAhhQUIAMcJACGGBQgAxwkAIYcFCADHCQAhiAUEANwJACGJBQQA3AkAIYoFQACzBwAhAgAAAPoBACAjAACHAgAgGYAEAgCgBwAhgQQBAJ4HACGEBAEAngcAIZsEAQCeBwAhpARAALMHACGlBEAAswcAIfcEBADcCQAh-AQEANwJACH5BAIAoAcAIfoEAgCgBwAh_AQBAJ4HACH9BAEAngcAIf4EAQCeBwAh_wQEANwJACGABQQA3AkAIYEFAgCgBwAhggUIAMcJACGDBQQA3AkAIYQFBADcCQAhhQUIAMcJACGGBQgAxwkAIYcFCADHCQAhiAUEANwJACGJBQQA3AkAIYoFQACzBwAhAgAAAP0BACAjAACJAgAgAgAAAP0BACAjAACJAgAgAwAAAPoBACAqAACCAgAgKwAAhwIAIAEAAAD6AQAgAQAAAP0BACAFBgAA3QkAIDAAAN4JACAxAADhCQAgMgAA4AkAIDMAAN8JACAc_QMAAOAGADD-AwAAkAIAEP8DAADgBgAwgAQCAIwGACGBBAEAjQYAIYQEAQCNBgAhmwQBAI0GACGkBEAAnQYAIaUEQACdBgAh9wQEANoGACH4BAQA2gYAIfkEAgCMBgAh-gQCAIwGACH8BAEAjQYAIf0EAQCNBgAh_gQBAI0GACH_BAQA2gYAIYAFBADaBgAhgQUCAIwGACGCBQgAzgYAIYMFBADaBgAhhAUEANoGACGFBQgAzgYAIYYFCADOBgAhhwUIAM4GACGIBQQA2gYAIYkFBADaBgAhigVAAJ0GACEDAAAA_QEAIAEAAI8CADAvAACQAgAgAwAAAP0BACABAAD-AQAwAgAA-gEAIBD9AwAA3QYAMP4DAACWAgAQ_wMAAN0GADCABAIAAAABgQQBALkGACGkBEAAugYAIcAEQAC6BgAh8wQIANEGACH0BAQA3gYAIfUECADRBgAh9gQEAN4GACH3BAQA3gYAIfgEBADeBgAh-QQCALgGACH6BAIAuAYAIfsEAADfBgAgAQAAAJMCACABAAAAkwIAIA_9AwAA3QYAMP4DAACWAgAQ_wMAAN0GADCABAIAuAYAIYEEAQC5BgAhpARAALoGACHABEAAugYAIfMECADRBgAh9AQEAN4GACH1BAgA0QYAIfYEBADeBgAh9wQEAN4GACH4BAQA3gYAIfkEAgC4BgAh-gQCALgGACEAAwAAAJYCACABAACXAgAwAgAAkwIAIAMAAACWAgAgAQAAlwIAMAIAAJMCACADAAAAlgIAIAEAAJcCADACAACTAgAgDIAEAgAAAAGBBAEAAAABpARAAAAAAcAEQAAAAAHzBAgAAAAB9AQEAAAAAfUECAAAAAH2BAQAAAAB9wQEAAAAAfgEBAAAAAH5BAIAAAAB-gQCAAAAAQEjAACbAgAgDIAEAgAAAAGBBAEAAAABpARAAAAAAcAEQAAAAAHzBAgAAAAB9AQEAAAAAfUECAAAAAH2BAQAAAAB9wQEAAAAAfgEBAAAAAH5BAIAAAAB-gQCAAAAAQEjAACdAgAwASMAAJ0CADAMgAQCAKAHACGBBAEAngcAIaQEQACzBwAhwARAALMHACHzBAgAxwkAIfQEBADcCQAh9QQIAMcJACH2BAQA3AkAIfcEBADcCQAh-AQEANwJACH5BAIAoAcAIfoEAgCgBwAhAgAAAJMCACAjAACgAgAgDIAEAgCgBwAhgQQBAJ4HACGkBEAAswcAIcAEQACzBwAh8wQIAMcJACH0BAQA3AkAIfUECADHCQAh9gQEANwJACH3BAQA3AkAIfgEBADcCQAh-QQCAKAHACH6BAIAoAcAIQIAAACWAgAgIwAAogIAIAIAAACWAgAgIwAAogIAIAMAAACTAgAgKgAAmwIAICsAAKACACABAAAAkwIAIAEAAACWAgAgBQYAANcJACAwAADYCQAgMQAA2wkAIDIAANoJACAzAADZCQAgD_0DAADZBgAw_gMAAKkCABD_AwAA2QYAMIAEAgCMBgAhgQQBAI0GACGkBEAAnQYAIcAEQACdBgAh8wQIAM4GACH0BAQA2gYAIfUECADOBgAh9gQEANoGACH3BAQA2gYAIfgEBADaBgAh-QQCAIwGACH6BAIAjAYAIQMAAACWAgAgAQAAqAIAMC8AAKkCACADAAAAlgIAIAEAAJcCADACAACTAgAgDP0DAADYBgAw_gMAAK8CABD_AwAA2AYAMIAEAgAAAAGEBAEAAAABmwQBALkGACGkBEAAugYAIaUEQAC6BgAh7wQBALkGACHwBAIAuAYAIfEEAQC5BgAh8gRAALoGACEBAAAArAIAIAEAAACsAgAgDP0DAADYBgAw_gMAAK8CABD_AwAA2AYAMIAEAgC4BgAhhAQBALkGACGbBAEAuQYAIaQEQAC6BgAhpQRAALoGACHvBAEAuQYAIfAEAgC4BgAh8QQBALkGACHyBEAAugYAIQADAAAArwIAIAEAALACADACAACsAgAgAwAAAK8CACABAACwAgAwAgAArAIAIAMAAACvAgAgAQAAsAIAMAIAAKwCACAJgAQCAAAAAYQEAQAAAAGbBAEAAAABpARAAAAAAaUEQAAAAAHvBAEAAAAB8AQCAAAAAfEEAQAAAAHyBEAAAAABASMAALQCACAJgAQCAAAAAYQEAQAAAAGbBAEAAAABpARAAAAAAaUEQAAAAAHvBAEAAAAB8AQCAAAAAfEEAQAAAAHyBEAAAAABASMAALYCADABIwAAtgIAMAmABAIAoAcAIYQEAQCeBwAhmwQBAJ4HACGkBEAAswcAIaUEQACzBwAh7wQBAJ4HACHwBAIAoAcAIfEEAQCeBwAh8gRAALMHACECAAAArAIAICMAALkCACAJgAQCAKAHACGEBAEAngcAIZsEAQCeBwAhpARAALMHACGlBEAAswcAIe8EAQCeBwAh8AQCAKAHACHxBAEAngcAIfIEQACzBwAhAgAAAK8CACAjAAC7AgAgAgAAAK8CACAjAAC7AgAgAwAAAKwCACAqAAC0AgAgKwAAuQIAIAEAAACsAgAgAQAAAK8CACAFBgAA0gkAIDAAANMJACAxAADWCQAgMgAA1QkAIDMAANQJACAM_QMAANcGADD-AwAAwgIAEP8DAADXBgAwgAQCAIwGACGEBAEAjQYAIZsEAQCNBgAhpARAAJ0GACGlBEAAnQYAIe8EAQCNBgAh8AQCAIwGACHxBAEAjQYAIfIEQACdBgAhAwAAAK8CACABAADBAgAwLwAAwgIAIAMAAACvAgAgAQAAsAIAMAIAAKwCACAI_QMAANYGADD-AwAAyAIAEP8DAADWBgAwgAQCAAAAAaQEQAC6BgAhpQRAALoGACHtBAEAAAAB7gQCALgGACEBAAAAxQIAIAEAAADFAgAgCP0DAADWBgAw_gMAAMgCABD_AwAA1gYAMIAEAgC4BgAhpARAALoGACGlBEAAugYAIe0EAQC5BgAh7gQCALgGACEAAwAAAMgCACABAADJAgAwAgAAxQIAIAMAAADIAgAgAQAAyQIAMAIAAMUCACADAAAAyAIAIAEAAMkCADACAADFAgAgBYAEAgAAAAGkBEAAAAABpQRAAAAAAe0EAQAAAAHuBAIAAAABASMAAM0CACAFgAQCAAAAAaQEQAAAAAGlBEAAAAAB7QQBAAAAAe4EAgAAAAEBIwAAzwIAMAEjAADPAgAwBYAEAgCgBwAhpARAALMHACGlBEAAswcAIe0EAQCeBwAh7gQCAKAHACECAAAAxQIAICMAANICACAFgAQCAKAHACGkBEAAswcAIaUEQACzBwAh7QQBAJ4HACHuBAIAoAcAIQIAAADIAgAgIwAA1AIAIAIAAADIAgAgIwAA1AIAIAMAAADFAgAgKgAAzQIAICsAANICACABAAAAxQIAIAEAAADIAgAgBQYAAM0JACAwAADOCQAgMQAA0QkAIDIAANAJACAzAADPCQAgCP0DAADVBgAw_gMAANsCABD_AwAA1QYAMIAEAgCMBgAhpARAAJ0GACGlBEAAnQYAIe0EAQCNBgAh7gQCAIwGACEDAAAAyAIAIAEAANoCADAvAADbAgAgAwAAAMgCACABAADJAgAwAgAAxQIAIAr9AwAA1AYAMP4DAADhAgAQ_wMAANQGADCABAIAAAABpARAALoGACGlBEAAugYAIekEAQAAAAHqBAEAxQYAIesEQAC6BgAh7AQAAL8GACABAAAA3gIAIAEAAADeAgAgCv0DAADUBgAw_gMAAOECABD_AwAA1AYAMIAEAgC4BgAhpARAALoGACGlBEAAugYAIekEAQC5BgAh6gQBAMUGACHrBEAAugYAIewEAAC_BgAgAuoEAACYBwAg7AQAAJgHACADAAAA4QIAIAEAAOICADACAADeAgAgAwAAAOECACABAADiAgAwAgAA3gIAIAMAAADhAgAgAQAA4gIAMAIAAN4CACAHgAQCAAAAAaQEQAAAAAGlBEAAAAAB6QQBAAAAAeoEAQAAAAHrBEAAAAAB7ASAAAAAAQEjAADmAgAgB4AEAgAAAAGkBEAAAAABpQRAAAAAAekEAQAAAAHqBAEAAAAB6wRAAAAAAewEgAAAAAEBIwAA6AIAMAEjAADoAgAwB4AEAgCgBwAhpARAALMHACGlBEAAswcAIekEAQCeBwAh6gQBAJ8HACHrBEAAswcAIewEgAAAAAECAAAA3gIAICMAAOsCACAHgAQCAKAHACGkBEAAswcAIaUEQACzBwAh6QQBAJ4HACHqBAEAnwcAIesEQACzBwAh7ASAAAAAAQIAAADhAgAgIwAA7QIAIAIAAADhAgAgIwAA7QIAIAMAAADeAgAgKgAA5gIAICsAAOsCACABAAAA3gIAIAEAAADhAgAgBwYAAMgJACAwAADJCQAgMQAAzAkAIDIAAMsJACAzAADKCQAg6gQAAJgHACDsBAAAmAcAIAr9AwAA0wYAMP4DAAD0AgAQ_wMAANMGADCABAIAjAYAIaQEQACdBgAhpQRAAJ0GACHpBAEAjQYAIeoEAQCOBgAh6wRAAJ0GACHsBAAAqgYAIAMAAADhAgAgAQAA8wIAMC8AAPQCACADAAAA4QIAIAEAAOICADACAADeAgAgC_0DAADQBgAw_gMAAPoCABD_AwAA0AYAMIAEAgAAAAGkBEAAugYAIb0EQAC6BgAhvwQBALkGACHlBAEAuQYAIeYEAQC5BgAh5wQIANEGACHoBAAA0gYAIAEAAAD3AgAgAQAAAPcCACAK_QMAANAGADD-AwAA-gIAEP8DAADQBgAwgAQCALgGACGkBEAAugYAIb0EQAC6BgAhvwQBALkGACHlBAEAuQYAIeYEAQC5BgAh5wQIANEGACEAAwAAAPoCACABAAD7AgAwAgAA9wIAIAMAAAD6AgAgAQAA-wIAMAIAAPcCACADAAAA-gIAIAEAAPsCADACAAD3AgAgB4AEAgAAAAGkBEAAAAABvQRAAAAAAb8EAQAAAAHlBAEAAAAB5gQBAAAAAecECAAAAAEBIwAA_wIAIAeABAIAAAABpARAAAAAAb0EQAAAAAG_BAEAAAAB5QQBAAAAAeYEAQAAAAHnBAgAAAABASMAAIEDADABIwAAgQMAMAeABAIAoAcAIaQEQACzBwAhvQRAALMHACG_BAEAngcAIeUEAQCeBwAh5gQBAJ4HACHnBAgAxwkAIQIAAAD3AgAgIwAAhAMAIAeABAIAoAcAIaQEQACzBwAhvQRAALMHACG_BAEAngcAIeUEAQCeBwAh5gQBAJ4HACHnBAgAxwkAIQIAAAD6AgAgIwAAhgMAIAIAAAD6AgAgIwAAhgMAIAMAAAD3AgAgKgAA_wIAICsAAIQDACABAAAA9wIAIAEAAAD6AgAgBQYAAMIJACAwAADDCQAgMQAAxgkAIDIAAMUJACAzAADECQAgCv0DAADNBgAw_gMAAI0DABD_AwAAzQYAMIAEAgCMBgAhpARAAJ0GACG9BEAAnQYAIb8EAQCNBgAh5QQBAI0GACHmBAEAjQYAIecECADOBgAhAwAAAPoCACABAACMAwAwLwAAjQMAIAMAAAD6AgAgAQAA-wIAMAIAAPcCACAJ_QMAAMwGADD-AwAAkwMAEP8DAADMBgAwgAQCAAAAAZAEAQC5BgAhmwQBALkGACGlBEAAugYAIeMEAQAAAAHkBAEAuQYAIQEAAACQAwAgAQAAAJADACAJ_QMAAMwGADD-AwAAkwMAEP8DAADMBgAwgAQCALgGACGQBAEAuQYAIZsEAQC5BgAhpQRAALoGACHjBAEAuQYAIeQEAQC5BgAhAAMAAACTAwAgAQAAlAMAMAIAAJADACADAAAAkwMAIAEAAJQDADACAACQAwAgAwAAAJMDACABAACUAwAwAgAAkAMAIAaABAIAAAABkAQBAAAAAZsEAQAAAAGlBEAAAAAB4wQBAAAAAeQEAQAAAAEBIwAAmAMAIAaABAIAAAABkAQBAAAAAZsEAQAAAAGlBEAAAAAB4wQBAAAAAeQEAQAAAAEBIwAAmgMAMAEjAACaAwAwBoAEAgCgBwAhkAQBAJ4HACGbBAEAngcAIaUEQACzBwAh4wQBAJ4HACHkBAEAngcAIQIAAACQAwAgIwAAnQMAIAaABAIAoAcAIZAEAQCeBwAhmwQBAJ4HACGlBEAAswcAIeMEAQCeBwAh5AQBAJ4HACECAAAAkwMAICMAAJ8DACACAAAAkwMAICMAAJ8DACADAAAAkAMAICoAAJgDACArAACdAwAgAQAAAJADACABAAAAkwMAIAUGAAC9CQAgMAAAvgkAIDEAAMEJACAyAADACQAgMwAAvwkAIAn9AwAAywYAMP4DAACmAwAQ_wMAAMsGADCABAIAjAYAIZAEAQCNBgAhmwQBAI0GACGlBEAAnQYAIeMEAQCNBgAh5AQBAI0GACEDAAAAkwMAIAEAAKUDADAvAACmAwAgAwAAAJMDACABAACUAwAwAgAAkAMAIAiMAgAAuwYAIP0DAAC3BgAw_gMAALsDABD_AwAAtwYAMIAEAgAAAAGEBAEAuQYAIaQEQAC6BgAh0QQBAAAAAQEAAACpAwAgF4gCAADIBgAgigIAAMkGACCLAgAAygYAIP0DAADEBgAw_gMAAKsDABD_AwAAxAYAMIAEAgC4BgAhhAQBALkGACGkBEAAugYAIdAEAQC5BgAh0QQBALkGACHSBAEAuQYAIdMEAQDFBgAh1AQCALgGACHVBAIAuAYAIdYEAgC4BgAh1wQCALgGACHYBAIAuAYAIdkEAgC4BgAh2gQCAMYGACHbBEAAwwYAIdwEIADHBgAh3QRAAMMGACEHiAIAALoJACCKAgAAuwkAIIsCAAC8CQAg0wQAAJgHACDaBAAAmAcAINsEAACYBwAg3QQAAJgHACAXiAIAAMgGACCKAgAAyQYAIIsCAADKBgAg_QMAAMQGADD-AwAAqwMAEP8DAADEBgAwgAQCAAAAAYQEAQC5BgAhpARAALoGACHQBAEAAAAB0QQBALkGACHSBAEAuQYAIdMEAQDFBgAh1AQCALgGACHVBAIAuAYAIdYEAgC4BgAh1wQCALgGACHYBAIAuAYAIdkEAgC4BgAh2gQCAMYGACHbBEAAwwYAIdwEIADHBgAh3QRAAMMGACEDAAAAqwMAIAEAAKwDADACAACtAwAgE4kCAADABgAg_QMAAMIGADD-AwAArwMAEP8DAADCBgAwgAQCALgGACGkBEAAugYAIb0EQAC6BgAhwwQCALgGACHFBAgAvgYAIcYECAC-BgAhxwQAAL8GACDIBAgAvgYAIckECAC-BgAhygQIAL4GACHLBAgAvgYAIcwECAC-BgAhzQQIAL4GACHOBAEAuQYAIc8EQADDBgAhC4kCAAC5CQAgxQQAAJgHACDGBAAAmAcAIMcEAACYBwAgyAQAAJgHACDJBAAAmAcAIMoEAACYBwAgywQAAJgHACDMBAAAmAcAIM0EAACYBwAgzwQAAJgHACAUiQIAAMAGACD9AwAAwgYAMP4DAACvAwAQ_wMAAMIGADCABAIAAAABpARAALoGACG9BEAAugYAIcMEAgC4BgAhxQQIAL4GACHGBAgAvgYAIccEAAC_BgAgyAQIAL4GACHJBAgAvgYAIcoECAC-BgAhywQIAL4GACHMBAgAvgYAIc0ECAC-BgAhzgQBALkGACHPBEAAwwYAIeIEAADBBgAgAwAAAK8DACABAACwAwAwAgAAsQMAIAuJAgAAwAYAIP0DAAC9BgAw_gMAALMDABD_AwAAvQYAMIAEAgC4BgAhpARAALoGACHDBAIAuAYAIcQEQAC6BgAhxQQIAL4GACHGBAgAvgYAIccEAAC_BgAgBIkCAAC5CQAgxQQAAJgHACDGBAAAmAcAIMcEAACYBwAgDIkCAADABgAg_QMAAL0GADD-AwAAswMAEP8DAAC9BgAwgAQCAAAAAaQEQAC6BgAhwwQCALgGACHEBEAAugYAIcUECAC-BgAhxgQIAL4GACHHBAAAvwYAIOEEAAC8BgAgAwAAALMDACABAAC0AwAwAgAAtQMAIAEAAACvAwAgAQAAALMDACABAAAAqwMAIAEAAACpAwAgCIwCAAC7BgAg_QMAALcGADD-AwAAuwMAEP8DAAC3BgAwgAQCALgGACGEBAEAuQYAIaQEQAC6BgAh0QQBALkGACEBjAIAALgJACADAAAAuwMAIAEAALwDADACAACpAwAgAwAAALsDACABAAC8AwAwAgAAqQMAIAMAAAC7AwAgAQAAvAMAMAIAAKkDACAFjAIAALcJACCABAIAAAABhAQBAAAAAaQEQAAAAAHRBAEAAAABASMAAMADACAEgAQCAAAAAYQEAQAAAAGkBEAAAAAB0QQBAAAAAQEjAADCAwAwASMAAMIDADAFjAIAAKoJACCABAIAoAcAIYQEAQCeBwAhpARAALMHACHRBAEAngcAIQIAAACpAwAgIwAAxQMAIASABAIAoAcAIYQEAQCeBwAhpARAALMHACHRBAEAngcAIQIAAAC7AwAgIwAAxwMAIAIAAAC7AwAgIwAAxwMAIAMAAACpAwAgKgAAwAMAICsAAMUDACABAAAAqQMAIAEAAAC7AwAgBQYAAKUJACAwAACmCQAgMQAAqQkAIDIAAKgJACAzAACnCQAgB_0DAAC2BgAw_gMAAM4DABD_AwAAtgYAMIAEAgCMBgAhhAQBAI0GACGkBEAAnQYAIdEEAQCNBgAhAwAAALsDACABAADNAwAwLwAAzgMAIAMAAAC7AwAgAQAAvAMAMAIAAKkDACABAAAArQMAIAEAAACtAwAgAwAAAKsDACABAACsAwAwAgAArQMAIAMAAACrAwAgAQAArAMAMAIAAK0DACADAAAAqwMAIAEAAKwDADACAACtAwAgFIgCAACiCQAgigIAAKMJACCLAgAApAkAIIAEAgAAAAGEBAEAAAABpARAAAAAAdAEAQAAAAHRBAEAAAAB0gQBAAAAAdMEAQAAAAHUBAIAAAAB1QQCAAAAAdYEAgAAAAHXBAIAAAAB2AQCAAAAAdkEAgAAAAHaBAIAAAAB2wRAAAAAAdwEIAAAAAHdBEAAAAABASMAANYDACARgAQCAAAAAYQEAQAAAAGkBEAAAAAB0AQBAAAAAdEEAQAAAAHSBAEAAAAB0wQBAAAAAdQEAgAAAAHVBAIAAAAB1gQCAAAAAdcEAgAAAAHYBAIAAAAB2QQCAAAAAdoEAgAAAAHbBEAAAAAB3AQgAAAAAd0EQAAAAAEBIwAA2AMAMAEjAADYAwAwFIgCAACHCQAgigIAAIgJACCLAgAAiQkAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIdAEAQCeBwAh0QQBAJ4HACHSBAEAngcAIdMEAQCfBwAh1AQCAKAHACHVBAIAoAcAIdYEAgCgBwAh1wQCAKAHACHYBAIAoAcAIdkEAgCgBwAh2gQCALQHACHbBEAAsQcAIdwEIACyBwAh3QRAALEHACECAAAArQMAICMAANsDACARgAQCAKAHACGEBAEAngcAIaQEQACzBwAh0AQBAJ4HACHRBAEAngcAIdIEAQCeBwAh0wQBAJ8HACHUBAIAoAcAIdUEAgCgBwAh1gQCAKAHACHXBAIAoAcAIdgEAgCgBwAh2QQCAKAHACHaBAIAtAcAIdsEQACxBwAh3AQgALIHACHdBEAAsQcAIQIAAACrAwAgIwAA3QMAIAIAAACrAwAgIwAA3QMAIAMAAACtAwAgKgAA1gMAICsAANsDACABAAAArQMAIAEAAACrAwAgCQYAAIIJACAwAACDCQAgMQAAhgkAIDIAAIUJACAzAACECQAg0wQAAJgHACDaBAAAmAcAINsEAACYBwAg3QQAAJgHACAU_QMAALUGADD-AwAA5AMAEP8DAAC1BgAwgAQCAIwGACGEBAEAjQYAIaQEQACdBgAh0AQBAI0GACHRBAEAjQYAIdIEAQCNBgAh0wQBAI4GACHUBAIAjAYAIdUEAgCMBgAh1gQCAIwGACHXBAIAjAYAIdgEAgCMBgAh2QQCAIwGACHaBAIAmQYAIdsEQACaBgAh3AQgAJwGACHdBEAAmgYAIQMAAACrAwAgAQAA4wMAMC8AAOQDACADAAAAqwMAIAEAAKwDADACAACtAwAgAQAAALEDACABAAAAsQMAIAMAAACvAwAgAQAAsAMAMAIAALEDACADAAAArwMAIAEAALADADACAACxAwAgAwAAAK8DACABAACwAwAwAgAAsQMAIBCJAgAAgQkAIIAEAgAAAAGkBEAAAAABvQRAAAAAAcMEAgAAAAHFBAgAAAABxgQIAAAAAccEgAAAAAHIBAgAAAAByQQIAAAAAcoECAAAAAHLBAgAAAABzAQIAAAAAc0ECAAAAAHOBAEAAAABzwRAAAAAAQEjAADsAwAgD4AEAgAAAAGkBEAAAAABvQRAAAAAAcMEAgAAAAHFBAgAAAABxgQIAAAAAccEgAAAAAHIBAgAAAAByQQIAAAAAcoECAAAAAHLBAgAAAABzAQIAAAAAc0ECAAAAAHOBAEAAAABzwRAAAAAAQEjAADuAwAwASMAAO4DADAQiQIAAIAJACCABAIAoAcAIaQEQACzBwAhvQRAALMHACHDBAIAoAcAIcUECAD4CAAhxgQIAPgIACHHBIAAAAAByAQIAPgIACHJBAgA-AgAIcoECAD4CAAhywQIAPgIACHMBAgA-AgAIc0ECAD4CAAhzgQBAJ4HACHPBEAAsQcAIQIAAACxAwAgIwAA8QMAIA-ABAIAoAcAIaQEQACzBwAhvQRAALMHACHDBAIAoAcAIcUECAD4CAAhxgQIAPgIACHHBIAAAAAByAQIAPgIACHJBAgA-AgAIcoECAD4CAAhywQIAPgIACHMBAgA-AgAIc0ECAD4CAAhzgQBAJ4HACHPBEAAsQcAIQIAAACvAwAgIwAA8wMAIAIAAACvAwAgIwAA8wMAIAMAAACxAwAgKgAA7AMAICsAAPEDACABAAAAsQMAIAEAAACvAwAgDwYAAPsIACAwAAD8CAAgMQAA_wgAIDIAAP4IACAzAAD9CAAgxQQAAJgHACDGBAAAmAcAIMcEAACYBwAgyAQAAJgHACDJBAAAmAcAIMoEAACYBwAgywQAAJgHACDMBAAAmAcAIM0EAACYBwAgzwQAAJgHACAS_QMAALQGADD-AwAA-gMAEP8DAAC0BgAwgAQCAIwGACGkBEAAnQYAIb0EQACdBgAhwwQCAIwGACHFBAgAsgYAIcYECACyBgAhxwQAAKoGACDIBAgAsgYAIckECACyBgAhygQIALIGACHLBAgAsgYAIcwECACyBgAhzQQIALIGACHOBAEAjQYAIc8EQACaBgAhAwAAAK8DACABAAD5AwAwLwAA-gMAIAMAAACvAwAgAQAAsAMAMAIAALEDACABAAAAtQMAIAEAAAC1AwAgAwAAALMDACABAAC0AwAwAgAAtQMAIAMAAACzAwAgAQAAtAMAMAIAALUDACADAAAAswMAIAEAALQDADACAAC1AwAgCIkCAAD6CAAggAQCAAAAAaQEQAAAAAHDBAIAAAABxARAAAAAAcUECAAAAAHGBAgAAAABxwSAAAAAAQEjAACCBAAgB4AEAgAAAAGkBEAAAAABwwQCAAAAAcQEQAAAAAHFBAgAAAABxgQIAAAAAccEgAAAAAEBIwAAhAQAMAEjAACEBAAwCIkCAAD5CAAggAQCAKAHACGkBEAAswcAIcMEAgCgBwAhxARAALMHACHFBAgA-AgAIcYECAD4CAAhxwSAAAAAAQIAAAC1AwAgIwAAhwQAIAeABAIAoAcAIaQEQACzBwAhwwQCAKAHACHEBEAAswcAIcUECAD4CAAhxgQIAPgIACHHBIAAAAABAgAAALMDACAjAACJBAAgAgAAALMDACAjAACJBAAgAwAAALUDACAqAACCBAAgKwAAhwQAIAEAAAC1AwAgAQAAALMDACAIBgAA8wgAIDAAAPQIACAxAAD3CAAgMgAA9ggAIDMAAPUIACDFBAAAmAcAIMYEAACYBwAgxwQAAJgHACAK_QMAALEGADD-AwAAkAQAEP8DAACxBgAwgAQCAIwGACGkBEAAnQYAIcMEAgCMBgAhxARAAJ0GACHFBAgAsgYAIcYECACyBgAhxwQAAKoGACADAAAAswMAIAEAAI8EADAvAACQBAAgAwAAALMDACABAAC0AwAwAgAAtQMAIAEAAAAFACABAAAABQAgAwAAAAMAIAEAAAQAMAIAAAUAIAMAAAADACABAAAEADACAAAFACADAAAAAwAgAQAABAAwAgAABQAgDgMAAPEIACAFAADyCAAggAQCAAAAAYQEAQAAAAGQBAIAAAABoAQCAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbUEAQAAAAG6BAEAAAABwAQBAAAAAcEEAgAAAAHCBAEAAAABASMAAJgEACAMgAQCAAAAAYQEAQAAAAGQBAIAAAABoAQCAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbUEAQAAAAG6BAEAAAABwAQBAAAAAcEEAgAAAAHCBAEAAAABASMAAJoEADABIwAAmgQAMA4DAADjCAAgBQAA5AgAIIAEAgCgBwAhhAQBAJ4HACGQBAIAoAcAIaAEAgCgBwAhoQQgALIHACGkBEAAswcAIaUEQACzBwAhtQQBAJ4HACG6BAEAngcAIcAEAQCeBwAhwQQCAKAHACHCBAEAngcAIQIAAAAFACAjAACdBAAgDIAEAgCgBwAhhAQBAJ4HACGQBAIAoAcAIaAEAgCgBwAhoQQgALIHACGkBEAAswcAIaUEQACzBwAhtQQBAJ4HACG6BAEAngcAIcAEAQCeBwAhwQQCAKAHACHCBAEAngcAIQIAAAADACAjAACfBAAgAgAAAAMAICMAAJ8EACADAAAABQAgKgAAmAQAICsAAJ0EACABAAAABQAgAQAAAAMAIAUGAADeCAAgMAAA3wgAIDEAAOIIACAyAADhCAAgMwAA4AgAIA_9AwAAsAYAMP4DAACmBAAQ_wMAALAGADCABAIAjAYAIYQEAQCNBgAhkAQCAIwGACGgBAIAjAYAIaEEIACcBgAhpARAAJ0GACGlBEAAnQYAIbUEAQCNBgAhugQBAI0GACHABAEAjQYAIcEEAgCMBgAhwgQBAI0GACEDAAAAAwAgAQAApQQAMC8AAKYEACADAAAAAwAgAQAABAAwAgAABQAgAQAAAAkAIAEAAAAJACADAAAABwAgAQAACAAwAgAACQAgAwAAAAcAIAEAAAgAMAIAAAkAIAMAAAAHACABAAAIADACAAAJACAKAwAA3QgAIAQAANwIACCABAIAAAABkAQCAAAAAaQEQAAAAAG6BAEAAAABvAQCAAAAAb0EAQAAAAG-BAIAAAABvwQBAAAAAQEjAACuBAAgCIAEAgAAAAGQBAIAAAABpARAAAAAAboEAQAAAAG8BAIAAAABvQQBAAAAAb4EAgAAAAG_BAEAAAABASMAALAEADABIwAAsAQAMAoDAADbCAAgBAAA2ggAIIAEAgCgBwAhkAQCAKAHACGkBEAAswcAIboEAQCeBwAhvAQCAKAHACG9BAEAngcAIb4EAgCgBwAhvwQBAJ4HACECAAAACQAgIwAAswQAIAiABAIAoAcAIZAEAgCgBwAhpARAALMHACG6BAEAngcAIbwEAgCgBwAhvQQBAJ4HACG-BAIAoAcAIb8EAQCeBwAhAgAAAAcAICMAALUEACACAAAABwAgIwAAtQQAIAMAAAAJACAqAACuBAAgKwAAswQAIAEAAAAJACABAAAABwAgBQYAANUIACAwAADWCAAgMQAA2QgAIDIAANgIACAzAADXCAAgC_0DAACvBgAw_gMAALwEABD_AwAArwYAMIAEAgCMBgAhkAQCAIwGACGkBEAAnQYAIboEAQCNBgAhvAQCAIwGACG9BAEAjQYAIb4EAgCMBgAhvwQBAI0GACEDAAAABwAgAQAAuwQAMC8AALwEACADAAAABwAgAQAACAAwAgAACQAgAQAAAA8AIAEAAAAPACADAAAADQAgAQAADgAwAgAADwAgAwAAAA0AIAEAAA4AMAIAAA8AIAMAAAANACABAAAOADACAAAPACAKAwAA0wgAIAoAANQIACCABAIAAAABkAQCAAAAAZcEAQAAAAGhBCAAAAABpARAAAAAAaUEQAAAAAG3BAEAAAABugQBAAAAAQEjAADEBAAgCIAEAgAAAAGQBAIAAAABlwQBAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbcEAQAAAAG6BAEAAAABASMAAMYEADABIwAAxgQAMAoDAADFCAAgCgAAxggAIIAEAgCgBwAhkAQCAKAHACGXBAEAngcAIaEEIACyBwAhpARAALMHACGlBEAAswcAIbcEAQCeBwAhugQBAJ4HACECAAAADwAgIwAAyQQAIAiABAIAoAcAIZAEAgCgBwAhlwQBAJ4HACGhBCAAsgcAIaQEQACzBwAhpQRAALMHACG3BAEAngcAIboEAQCeBwAhAgAAAA0AICMAAMsEACACAAAADQAgIwAAywQAIAMAAAAPACAqAADEBAAgKwAAyQQAIAEAAAAPACABAAAADQAgBQYAAMAIACAwAADBCAAgMQAAxAgAIDIAAMMIACAzAADCCAAgC_0DAACuBgAw_gMAANIEABD_AwAArgYAMIAEAgCMBgAhkAQCAIwGACGXBAEAjQYAIaEEIACcBgAhpARAAJ0GACGlBEAAnQYAIbcEAQCNBgAhugQBAI0GACEDAAAADQAgAQAA0QQAMC8AANIEACADAAAADQAgAQAADgAwAgAADwAgAQAAABMAIAEAAAATACADAAAAEQAgAQAAEgAwAgAAEwAgAwAAABEAIAEAABIAMAIAABMAIAMAAAARACABAAASADACAAATACAKCQAAvwgAIIAEAgAAAAGXBAEAAAABnARAAAAAAaAEAgAAAAGkBEAAAAABpQRAAAAAAbkEAgAAAAG6BAEAAAABuwQgAAAAAQEjAADaBAAgCYAEAgAAAAGXBAEAAAABnARAAAAAAaAEAgAAAAGkBEAAAAABpQRAAAAAAbkEAgAAAAG6BAEAAAABuwQgAAAAAQEjAADcBAAwASMAANwEADAKCQAAvggAIIAEAgCgBwAhlwQBAJ4HACGcBEAAsQcAIaAEAgCgBwAhpARAALMHACGlBEAAswcAIbkEAgCgBwAhugQBAJ4HACG7BCAAsgcAIQIAAAATACAjAADfBAAgCYAEAgCgBwAhlwQBAJ4HACGcBEAAsQcAIaAEAgCgBwAhpARAALMHACGlBEAAswcAIbkEAgCgBwAhugQBAJ4HACG7BCAAsgcAIQIAAAARACAjAADhBAAgAgAAABEAICMAAOEEACADAAAAEwAgKgAA2gQAICsAAN8EACABAAAAEwAgAQAAABEAIAYGAAC5CAAgMAAAuggAIDEAAL0IACAyAAC8CAAgMwAAuwgAIJwEAACYBwAgDP0DAACtBgAw_gMAAOgEABD_AwAArQYAMIAEAgCMBgAhlwQBAI0GACGcBEAAmgYAIaAEAgCMBgAhpARAAJ0GACGlBEAAnQYAIbkEAgCMBgAhugQBAI0GACG7BCAAnAYAIQMAAAARACABAADnBAAwLwAA6AQAIAMAAAARACABAAASADACAAATACABAAAAGAAgAQAAABgAIAMAAAAWACABAAAXADACAAAYACADAAAAFgAgAQAAFwAwAgAAGAAgAwAAABYAIAEAABcAMAIAABgAIAkDAAC4CAAgCoAAAAABgAQCAAAAAYQEAQAAAAGQBAIAAAABpARAAAAAAaUEQAAAAAG3BAEAAAABuAQgAAAAAQEjAADwBAAgCAqAAAAAAYAEAgAAAAGEBAEAAAABkAQCAAAAAaQEQAAAAAGlBEAAAAABtwQBAAAAAbgEIAAAAAEBIwAA8gQAMAEjAADyBAAwAQAAABoAIAkDAAC3CAAgCoAAAAABgAQCAKAHACGEBAEAngcAIZAEAgC0BwAhpARAALMHACGlBEAAswcAIbcEAQCeBwAhuAQgALIHACECAAAAGAAgIwAA9gQAIAgKgAAAAAGABAIAoAcAIYQEAQCeBwAhkAQCALQHACGkBEAAswcAIaUEQACzBwAhtwQBAJ4HACG4BCAAsgcAIQIAAAAWACAjAAD4BAAgAgAAABYAICMAAPgEACABAAAAGgAgAwAAABgAICoAAPAEACArAAD2BAAgAQAAABgAIAEAAAAWACAGBgAAsggAIDAAALMIACAxAAC2CAAgMgAAtQgAIDMAALQIACCQBAAAmAcAIAsKAACbBgAg_QMAAKwGADD-AwAAgAUAEP8DAACsBgAwgAQCAIwGACGEBAEAjQYAIZAEAgCZBgAhpARAAJ0GACGlBEAAnQYAIbcEAQCNBgAhuAQgAJwGACEDAAAAFgAgAQAA_wQAMC8AAIAFACADAAAAFgAgAQAAFwAwAgAAGAAgAQAAAB4AIAEAAAAeACADAAAAHAAgAQAAHQAwAgAAHgAgAwAAABwAIAEAAB0AMAIAAB4AIAMAAAAcACABAAAdADACAAAeACARDQAArggAIA8AAK8IACAXAACxCAAgGAAAsAgAIIAEAgAAAAGBBAEAAAABhAQBAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbAEAQAAAAGxBAIAAAABsgQgAAAAAbMEgAAAAAG0BIAAAAABtQQBAAAAAbYEAQAAAAEBIwAAiAUAIA2ABAIAAAABgQQBAAAAAYQEAQAAAAGhBCAAAAABpARAAAAAAaUEQAAAAAGwBAEAAAABsQQCAAAAAbIEIAAAAAGzBIAAAAABtASAAAAAAbUEAQAAAAG2BAEAAAABASMAAIoFADABIwAAigUAMBENAACJCAAgDwAAiggAIBcAAIwIACAYAACLCAAggAQCAKAHACGBBAEAngcAIYQEAQCeBwAhoQQgALIHACGkBEAAswcAIaUEQACzBwAhsAQBAJ4HACGxBAIAoAcAIbIEIACyBwAhswSAAAAAAbQEgAAAAAG1BAEAnwcAIbYEAQCeBwAhAgAAAB4AICMAAI0FACANgAQCAKAHACGBBAEAngcAIYQEAQCeBwAhoQQgALIHACGkBEAAswcAIaUEQACzBwAhsAQBAJ4HACGxBAIAoAcAIbIEIACyBwAhswSAAAAAAbQEgAAAAAG1BAEAnwcAIbYEAQCeBwAhAgAAABwAICMAAI8FACACAAAAHAAgIwAAjwUAIAMAAAAeACAqAACIBQAgKwAAjQUAIAEAAAAeACABAAAAHAAgBwYAAIQIACAwAACFCAAgMQAAiAgAIDIAAIcIACAzAACGCAAgtAQAAJgHACC1BAAAmAcAIBD9AwAAqQYAMP4DAACWBQAQ_wMAAKkGADCABAIAjAYAIYEEAQCNBgAhhAQBAI0GACGhBCAAnAYAIaQEQACdBgAhpQRAAJ0GACGwBAEAjQYAIbEEAgCMBgAhsgQgAJwGACGzBAAAmwYAILQEAACqBgAgtQQBAI4GACG2BAEAjQYAIQMAAAAcACABAACVBQAwLwAAlgUAIAMAAAAcACABAAAdADACAAAeACABAAAAIgAgAQAAACIAIAMAAAAgACABAAAhADACAAAiACADAAAAIAAgAQAAIQAwAgAAIgAgAwAAACAAIAEAACEAMAIAACIAIAkDAACDCAAgDgAAgggAIIAEAgAAAAGBBAEAAAABkAQCAAAAAZEEAQAAAAGUBAIAAAABpARAAAAAAa8EAQAAAAEBIwAAngUAIAeABAIAAAABgQQBAAAAAZAEAgAAAAGRBAEAAAABlAQCAAAAAaQEQAAAAAGvBAEAAAABASMAAKAFADABIwAAoAUAMAkDAACBCAAgDgAAgAgAIIAEAgCgBwAhgQQBAJ4HACGQBAIAoAcAIZEEAQCfBwAhlAQCAKAHACGkBEAAswcAIa8EAQCeBwAhAgAAACIAICMAAKMFACAHgAQCAKAHACGBBAEAngcAIZAEAgCgBwAhkQQBAJ8HACGUBAIAoAcAIaQEQACzBwAhrwQBAJ4HACECAAAAIAAgIwAApQUAIAIAAAAgACAjAAClBQAgAwAAACIAICoAAJ4FACArAACjBQAgAQAAACIAIAEAAAAgACAGBgAA-wcAIDAAAPwHACAxAAD_BwAgMgAA_gcAIDMAAP0HACCRBAAAmAcAIAr9AwAAqAYAMP4DAACsBQAQ_wMAAKgGADCABAIAjAYAIYEEAQCNBgAhkAQCAIwGACGRBAEAjgYAIZQEAgCMBgAhpARAAJ0GACGvBAEAjQYAIQMAAAAgACABAACrBQAwLwAArAUAIAMAAAAgACABAAAhADACAAAiACABAAAAJgAgAQAAACYAIAMAAAAkACABAAAlADACAAAmACADAAAAJAAgAQAAJQAwAgAAJgAgAwAAACQAIAEAACUAMAIAACYAIAwOAAD5BwAgFwAA-gcAIIAEAgAAAAGBBAEAAAABhAQBAAAAAZEEAQAAAAGUBAIAAAABoAQCAAAAAaQEQAAAAAGlBEAAAAABrQQBAAAAAa4EAQAAAAEBIwAAtAUAIAqABAIAAAABgQQBAAAAAYQEAQAAAAGRBAEAAAABlAQCAAAAAaAEAgAAAAGkBEAAAAABpQRAAAAAAa0EAQAAAAGuBAEAAAABASMAALYFADABIwAAtgUAMAwOAADuBwAgFwAA7wcAIIAEAgCgBwAhgQQBAJ4HACGEBAEAngcAIZEEAQCfBwAhlAQCAKAHACGgBAIAoAcAIaQEQACzBwAhpQRAALMHACGtBAEAnwcAIa4EAQCeBwAhAgAAACYAICMAALkFACAKgAQCAKAHACGBBAEAngcAIYQEAQCeBwAhkQQBAJ8HACGUBAIAoAcAIaAEAgCgBwAhpARAALMHACGlBEAAswcAIa0EAQCfBwAhrgQBAJ4HACECAAAAJAAgIwAAuwUAIAIAAAAkACAjAAC7BQAgAwAAACYAICoAALQFACArAAC5BQAgAQAAACYAIAEAAAAkACAHBgAA6QcAIDAAAOoHACAxAADtBwAgMgAA7AcAIDMAAOsHACCRBAAAmAcAIK0EAACYBwAgDf0DAACnBgAw_gMAAMIFABD_AwAApwYAMIAEAgCMBgAhgQQBAI0GACGEBAEAjQYAIZEEAQCOBgAhlAQCAIwGACGgBAIAjAYAIaQEQACdBgAhpQRAAJ0GACGtBAEAjgYAIa4EAQCNBgAhAwAAACQAIAEAAMEFADAvAADCBQAgAwAAACQAIAEAACUAMAIAACYAIAEAAAAqACABAAAAKgAgAwAAACgAIAEAACkAMAIAACoAIAMAAAAoACABAAApADACAAAqACADAAAAKAAgAQAAKQAwAgAAKgAgIA4AAOEHACAPAADmBwAgEAAA4gcAIBEAAOgHACASAADjBwAgEwAA5AcAIBQAAOUHACAWAADnBwAggAQCAAAAAYEEAQAAAAGRBAEAAAABkgQBAAAAAZMEAQAAAAGUBAIAAAABlQQCAAAAAZYEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGdBAIAAAABngQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABASMAAMoFACAYgAQCAAAAAYEEAQAAAAGRBAEAAAABkgQBAAAAAZMEAQAAAAGUBAIAAAABlQQCAAAAAZYEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGdBAIAAAABngQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABASMAAMwFADABIwAAzAUAMAEAAAAoACABAAAAGgAgIA4AALUHACAPAAC7BwAgEAAAtgcAIBEAALcHACASAAC4BwAgEwAAuQcAIBQAALoHACAWAAC8BwAggAQCAKAHACGBBAEAngcAIZEEAQCfBwAhkgQBAJ8HACGTBAEAnwcAIZQEAgCgBwAhlQQCAKAHACGWBAIAtAcAIZcEAQCeBwAhmAQBAJ4HACGZBAEAnwcAIZoEQACxBwAhmwQBAJ4HACGcBEAAsQcAIZ0EAgC0BwAhngQCAKAHACGfBIAAAAABoAQCAKAHACGhBCAAsgcAIaIEQACxBwAhowQBAJ8HACGkBEAAswcAIaUEQACzBwAhpgQCALQHACECAAAAKgAgIwAA0QUAIBiABAIAoAcAIYEEAQCeBwAhkQQBAJ8HACGSBAEAnwcAIZMEAQCfBwAhlAQCAKAHACGVBAIAoAcAIZYEAgC0BwAhlwQBAJ4HACGYBAEAngcAIZkEAQCfBwAhmgRAALEHACGbBAEAngcAIZwEQACxBwAhnQQCALQHACGeBAIAoAcAIZ8EgAAAAAGgBAIAoAcAIaEEIACyBwAhogRAALEHACGjBAEAnwcAIaQEQACzBwAhpQRAALMHACGmBAIAtAcAIQIAAAAoACAjAADTBQAgAgAAACgAICMAANMFACABAAAAKAAgAQAAABoAIAMAAAAqACAqAADKBQAgKwAA0QUAIAEAAAAqACABAAAAKAAgEAYAAKwHACAwAACtBwAgMQAAsAcAIDIAAK8HACAzAACuBwAgkQQAAJgHACCSBAAAmAcAIJMEAACYBwAglgQAAJgHACCZBAAAmAcAIJoEAACYBwAgnAQAAJgHACCdBAAAmAcAIKIEAACYBwAgowQAAJgHACCmBAAAmAcAIBv9AwAAmAYAMP4DAADcBQAQ_wMAAJgGADCABAIAjAYAIYEEAQCNBgAhkQQBAI4GACGSBAEAjgYAIZMEAQCOBgAhlAQCAIwGACGVBAIAjAYAIZYEAgCZBgAhlwQBAI0GACGYBAEAjQYAIZkEAQCOBgAhmgRAAJoGACGbBAEAjQYAIZwEQACaBgAhnQQCAJkGACGeBAIAjAYAIZ8EAACbBgAgoAQCAIwGACGhBCAAnAYAIaIEQACaBgAhowQBAI4GACGkBEAAnQYAIaUEQACdBgAhpgQCAJkGACEDAAAAKAAgAQAA2wUAMC8AANwFACADAAAAKAAgAQAAKQAwAgAAKgAgAQAAADEAIAEAAAAxACADAAAALwAgAQAAMAAwAgAAMQAgAwAAAC8AIAEAADAAMAIAADEAIAMAAAAvACABAAAwADACAAAxACAHAwAAqwcAIBUAAKoHACCABAIAAAABgQQBAAAAAYIEAQAAAAGDBAIAAAABkAQCAAAAAQEjAADkBQAgBYAEAgAAAAGBBAEAAAABggQBAAAAAYMEAgAAAAGQBAIAAAABASMAAOYFADABIwAA5gUAMAcDAACpBwAgFQAAqAcAIIAEAgCgBwAhgQQBAJ4HACGCBAEAnwcAIYMEAgCgBwAhkAQCAKAHACECAAAAMQAgIwAA6QUAIAWABAIAoAcAIYEEAQCeBwAhggQBAJ8HACGDBAIAoAcAIZAEAgCgBwAhAgAAAC8AICMAAOsFACACAAAALwAgIwAA6wUAIAMAAAAxACAqAADkBQAgKwAA6QUAIAEAAAAxACABAAAALwAgBgYAAKMHACAwAACkBwAgMQAApwcAIDIAAKYHACAzAAClBwAgggQAAJgHACAI_QMAAJcGADD-AwAA8gUAEP8DAACXBgAwgAQCAIwGACGBBAEAjQYAIYIEAQCOBgAhgwQCAIwGACGQBAIAjAYAIQMAAAAvACABAADxBQAwLwAA8gUAIAMAAAAvACABAAAwADACAAAxACABAAAANQAgAQAAADUAIAMAAAAzACABAAA0ADACAAA1ACADAAAAMwAgAQAANAAwAgAANQAgAwAAADMAIAEAADQAMAIAADUAIAYVAACiBwAggAQCAAAAAYEEAQAAAAGCBAEAAAABgwQCAAAAAYQEAQAAAAEBIwAA-gUAIAWABAIAAAABgQQBAAAAAYIEAQAAAAGDBAIAAAABhAQBAAAAAQEjAAD8BQAwASMAAPwFADAGFQAAoQcAIIAEAgCgBwAhgQQBAJ4HACGCBAEAnwcAIYMEAgCgBwAhhAQBAJ4HACECAAAANQAgIwAA_wUAIAWABAIAoAcAIYEEAQCeBwAhggQBAJ8HACGDBAIAoAcAIYQEAQCeBwAhAgAAADMAICMAAIEGACACAAAAMwAgIwAAgQYAIAMAAAA1ACAqAAD6BQAgKwAA_wUAIAEAAAA1ACABAAAAMwAgBgYAAJkHACAwAACaBwAgMQAAnQcAIDIAAJwHACAzAACbBwAgggQAAJgHACAI_QMAAIsGADD-AwAAiAYAEP8DAACLBgAwgAQCAIwGACGBBAEAjQYAIYIEAQCOBgAhgwQCAIwGACGEBAEAjQYAIQMAAAAzACABAACHBgAwLwAAiAYAIAMAAAAzACABAAA0ADACAAA1ACAI_QMAAIsGADD-AwAAiAYAEP8DAACLBgAwgAQCAIwGACGBBAEAjQYAIYIEAQCOBgAhgwQCAIwGACGEBAEAjQYAIQ0GAACTBgAgMAAAlgYAIDEAAJMGACAyAACTBgAgMwAAkwYAIIUEAgAAAAGGBAIAAAAEhwQCAAAABIgEAgAAAAGJBAIAAAABigQCAAAAAYsEAgAAAAGPBAIAlQYAIQ4GAACTBgAgMgAAlAYAIDMAAJQGACCFBAEAAAABhgQBAAAABIcEAQAAAASIBAEAAAABiQQBAAAAAYoEAQAAAAGLBAEAAAABjAQBAAAAAY0EAQAAAAGOBAEAAAABjwQBAJIGACEOBgAAkAYAIDIAAJEGACAzAACRBgAghQQBAAAAAYYEAQAAAAWHBAEAAAAFiAQBAAAAAYkEAQAAAAGKBAEAAAABiwQBAAAAAYwEAQAAAAGNBAEAAAABjgQBAAAAAY8EAQCPBgAhDgYAAJAGACAyAACRBgAgMwAAkQYAIIUEAQAAAAGGBAEAAAAFhwQBAAAABYgEAQAAAAGJBAEAAAABigQBAAAAAYsEAQAAAAGMBAEAAAABjQQBAAAAAY4EAQAAAAGPBAEAjwYAIQiFBAIAAAABhgQCAAAABYcEAgAAAAWIBAIAAAABiQQCAAAAAYoEAgAAAAGLBAIAAAABjwQCAJAGACELhQQBAAAAAYYEAQAAAAWHBAEAAAAFiAQBAAAAAYkEAQAAAAGKBAEAAAABiwQBAAAAAYwEAQAAAAGNBAEAAAABjgQBAAAAAY8EAQCRBgAhDgYAAJMGACAyAACUBgAgMwAAlAYAIIUEAQAAAAGGBAEAAAAEhwQBAAAABIgEAQAAAAGJBAEAAAABigQBAAAAAYsEAQAAAAGMBAEAAAABjQQBAAAAAY4EAQAAAAGPBAEAkgYAIQiFBAIAAAABhgQCAAAABIcEAgAAAASIBAIAAAABiQQCAAAAAYoEAgAAAAGLBAIAAAABjwQCAJMGACELhQQBAAAAAYYEAQAAAASHBAEAAAAEiAQBAAAAAYkEAQAAAAGKBAEAAAABiwQBAAAAAYwEAQAAAAGNBAEAAAABjgQBAAAAAY8EAQCUBgAhDQYAAJMGACAwAACWBgAgMQAAkwYAIDIAAJMGACAzAACTBgAghQQCAAAAAYYEAgAAAASHBAIAAAAEiAQCAAAAAYkEAgAAAAGKBAIAAAABiwQCAAAAAY8EAgCVBgAhCIUECAAAAAGGBAgAAAAEhwQIAAAABIgECAAAAAGJBAgAAAABigQIAAAAAYsECAAAAAGPBAgAlgYAIQj9AwAAlwYAMP4DAADyBQAQ_wMAAJcGADCABAIAjAYAIYEEAQCNBgAhggQBAI4GACGDBAIAjAYAIZAEAgCMBgAhG_0DAACYBgAw_gMAANwFABD_AwAAmAYAMIAEAgCMBgAhgQQBAI0GACGRBAEAjgYAIZIEAQCOBgAhkwQBAI4GACGUBAIAjAYAIZUEAgCMBgAhlgQCAJkGACGXBAEAjQYAIZgEAQCNBgAhmQQBAI4GACGaBEAAmgYAIZsEAQCNBgAhnARAAJoGACGdBAIAmQYAIZ4EAgCMBgAhnwQAAJsGACCgBAIAjAYAIaEEIACcBgAhogRAAJoGACGjBAEAjgYAIaQEQACdBgAhpQRAAJ0GACGmBAIAmQYAIQ0GAACQBgAgMAAApgYAIDEAAJAGACAyAACQBgAgMwAAkAYAIIUEAgAAAAGGBAIAAAAFhwQCAAAABYgEAgAAAAGJBAIAAAABigQCAAAAAYsEAgAAAAGPBAIApQYAIQsGAACQBgAgMgAApAYAIDMAAKQGACCFBEAAAAABhgRAAAAABYcEQAAAAAWIBEAAAAABiQRAAAAAAYoEQAAAAAGLBEAAAAABjwRAAKMGACEPBgAAkwYAIDIAAKIGACAzAACiBgAghQSAAAAAAYgEgAAAAAGJBIAAAAABigSAAAAAAYsEgAAAAAGPBIAAAAABpwQBAAAAAagEAQAAAAGpBAEAAAABqgSAAAAAAasEgAAAAAGsBIAAAAABBQYAAJMGACAyAAChBgAgMwAAoQYAIIUEIAAAAAGPBCAAoAYAIQsGAACTBgAgMgAAnwYAIDMAAJ8GACCFBEAAAAABhgRAAAAABIcEQAAAAASIBEAAAAABiQRAAAAAAYoEQAAAAAGLBEAAAAABjwRAAJ4GACELBgAAkwYAIDIAAJ8GACAzAACfBgAghQRAAAAAAYYEQAAAAASHBEAAAAAEiARAAAAAAYkEQAAAAAGKBEAAAAABiwRAAAAAAY8EQACeBgAhCIUEQAAAAAGGBEAAAAAEhwRAAAAABIgEQAAAAAGJBEAAAAABigRAAAAAAYsEQAAAAAGPBEAAnwYAIQUGAACTBgAgMgAAoQYAIDMAAKEGACCFBCAAAAABjwQgAKAGACEChQQgAAAAAY8EIAChBgAhDIUEgAAAAAGIBIAAAAABiQSAAAAAAYoEgAAAAAGLBIAAAAABjwSAAAAAAacEAQAAAAGoBAEAAAABqQQBAAAAAaoEgAAAAAGrBIAAAAABrASAAAAAAQsGAACQBgAgMgAApAYAIDMAAKQGACCFBEAAAAABhgRAAAAABYcEQAAAAAWIBEAAAAABiQRAAAAAAYoEQAAAAAGLBEAAAAABjwRAAKMGACEIhQRAAAAAAYYEQAAAAAWHBEAAAAAFiARAAAAAAYkEQAAAAAGKBEAAAAABiwRAAAAAAY8EQACkBgAhDQYAAJAGACAwAACmBgAgMQAAkAYAIDIAAJAGACAzAACQBgAghQQCAAAAAYYEAgAAAAWHBAIAAAAFiAQCAAAAAYkEAgAAAAGKBAIAAAABiwQCAAAAAY8EAgClBgAhCIUECAAAAAGGBAgAAAAFhwQIAAAABYgECAAAAAGJBAgAAAABigQIAAAAAYsECAAAAAGPBAgApgYAIQ39AwAApwYAMP4DAADCBQAQ_wMAAKcGADCABAIAjAYAIYEEAQCNBgAhhAQBAI0GACGRBAEAjgYAIZQEAgCMBgAhoAQCAIwGACGkBEAAnQYAIaUEQACdBgAhrQQBAI4GACGuBAEAjQYAIQr9AwAAqAYAMP4DAACsBQAQ_wMAAKgGADCABAIAjAYAIYEEAQCNBgAhkAQCAIwGACGRBAEAjgYAIZQEAgCMBgAhpARAAJ0GACGvBAEAjQYAIRD9AwAAqQYAMP4DAACWBQAQ_wMAAKkGADCABAIAjAYAIYEEAQCNBgAhhAQBAI0GACGhBCAAnAYAIaQEQACdBgAhpQRAAJ0GACGwBAEAjQYAIbEEAgCMBgAhsgQgAJwGACGzBAAAmwYAILQEAACqBgAgtQQBAI4GACG2BAEAjQYAIQ8GAACQBgAgMgAAqwYAIDMAAKsGACCFBIAAAAABiASAAAAAAYkEgAAAAAGKBIAAAAABiwSAAAAAAY8EgAAAAAGnBAEAAAABqAQBAAAAAakEAQAAAAGqBIAAAAABqwSAAAAAAawEgAAAAAEMhQSAAAAAAYgEgAAAAAGJBIAAAAABigSAAAAAAYsEgAAAAAGPBIAAAAABpwQBAAAAAagEAQAAAAGpBAEAAAABqgSAAAAAAasEgAAAAAGsBIAAAAABCwoAAJsGACD9AwAArAYAMP4DAACABQAQ_wMAAKwGADCABAIAjAYAIYQEAQCNBgAhkAQCAJkGACGkBEAAnQYAIaUEQACdBgAhtwQBAI0GACG4BCAAnAYAIQz9AwAArQYAMP4DAADoBAAQ_wMAAK0GADCABAIAjAYAIZcEAQCNBgAhnARAAJoGACGgBAIAjAYAIaQEQACdBgAhpQRAAJ0GACG5BAIAjAYAIboEAQCNBgAhuwQgAJwGACEL_QMAAK4GADD-AwAA0gQAEP8DAACuBgAwgAQCAIwGACGQBAIAjAYAIZcEAQCNBgAhoQQgAJwGACGkBEAAnQYAIaUEQACdBgAhtwQBAI0GACG6BAEAjQYAIQv9AwAArwYAMP4DAAC8BAAQ_wMAAK8GADCABAIAjAYAIZAEAgCMBgAhpARAAJ0GACG6BAEAjQYAIbwEAgCMBgAhvQQBAI0GACG-BAIAjAYAIb8EAQCNBgAhD_0DAACwBgAw_gMAAKYEABD_AwAAsAYAMIAEAgCMBgAhhAQBAI0GACGQBAIAjAYAIaAEAgCMBgAhoQQgAJwGACGkBEAAnQYAIaUEQACdBgAhtQQBAI0GACG6BAEAjQYAIcAEAQCNBgAhwQQCAIwGACHCBAEAjQYAIQr9AwAAsQYAMP4DAACQBAAQ_wMAALEGADCABAIAjAYAIaQEQACdBgAhwwQCAIwGACHEBEAAnQYAIcUECACyBgAhxgQIALIGACHHBAAAqgYAIA0GAACQBgAgMAAApgYAIDEAAKYGACAyAACmBgAgMwAApgYAIIUECAAAAAGGBAgAAAAFhwQIAAAABYgECAAAAAGJBAgAAAABigQIAAAAAYsECAAAAAGPBAgAswYAIQ0GAACQBgAgMAAApgYAIDEAAKYGACAyAACmBgAgMwAApgYAIIUECAAAAAGGBAgAAAAFhwQIAAAABYgECAAAAAGJBAgAAAABigQIAAAAAYsECAAAAAGPBAgAswYAIRL9AwAAtAYAMP4DAAD6AwAQ_wMAALQGADCABAIAjAYAIaQEQACdBgAhvQRAAJ0GACHDBAIAjAYAIcUECACyBgAhxgQIALIGACHHBAAAqgYAIMgECACyBgAhyQQIALIGACHKBAgAsgYAIcsECACyBgAhzAQIALIGACHNBAgAsgYAIc4EAQCNBgAhzwRAAJoGACEU_QMAALUGADD-AwAA5AMAEP8DAAC1BgAwgAQCAIwGACGEBAEAjQYAIaQEQACdBgAh0AQBAI0GACHRBAEAjQYAIdIEAQCNBgAh0wQBAI4GACHUBAIAjAYAIdUEAgCMBgAh1gQCAIwGACHXBAIAjAYAIdgEAgCMBgAh2QQCAIwGACHaBAIAmQYAIdsEQACaBgAh3AQgAJwGACHdBEAAmgYAIQf9AwAAtgYAMP4DAADOAwAQ_wMAALYGADCABAIAjAYAIYQEAQCNBgAhpARAAJ0GACHRBAEAjQYAIQiMAgAAuwYAIP0DAAC3BgAw_gMAALsDABD_AwAAtwYAMIAEAgC4BgAhhAQBALkGACGkBEAAugYAIdEEAQC5BgAhCIUEAgAAAAGGBAIAAAAEhwQCAAAABIgEAgAAAAGJBAIAAAABigQCAAAAAYsEAgAAAAGPBAIAkwYAIQuFBAEAAAABhgQBAAAABIcEAQAAAASIBAEAAAABiQQBAAAAAYoEAQAAAAGLBAEAAAABjAQBAAAAAY0EAQAAAAGOBAEAAAABjwQBAJQGACEIhQRAAAAAAYYEQAAAAASHBEAAAAAEiARAAAAAAYkEQAAAAAGKBEAAAAABiwRAAAAAAY8EQACfBgAhA94EAACrAwAg3wQAAKsDACDgBAAAqwMAIALDBAIAAAABxARAAAAAAQuJAgAAwAYAIP0DAAC9BgAw_gMAALMDABD_AwAAvQYAMIAEAgC4BgAhpARAALoGACHDBAIAuAYAIcQEQAC6BgAhxQQIAL4GACHGBAgAvgYAIccEAAC_BgAgCIUECAAAAAGGBAgAAAAFhwQIAAAABYgECAAAAAGJBAgAAAABigQIAAAAAYsECAAAAAGPBAgApgYAIQyFBIAAAAABiASAAAAAAYkEgAAAAAGKBIAAAAABiwSAAAAAAY8EgAAAAAGnBAEAAAABqAQBAAAAAakEAQAAAAGqBIAAAAABqwSAAAAAAawEgAAAAAEZiAIAAMgGACCKAgAAyQYAIIsCAADKBgAg_QMAAMQGADD-AwAAqwMAEP8DAADEBgAwgAQCALgGACGEBAEAuQYAIaQEQAC6BgAh0AQBALkGACHRBAEAuQYAIdIEAQC5BgAh0wQBAMUGACHUBAIAuAYAIdUEAgC4BgAh1gQCALgGACHXBAIAuAYAIdgEAgC4BgAh2QQCALgGACHaBAIAxgYAIdsEQADDBgAh3AQgAMcGACHdBEAAwwYAIaYFAACrAwAgpwUAAKsDACACvQRAAAAAAcMEAgAAAAETiQIAAMAGACD9AwAAwgYAMP4DAACvAwAQ_wMAAMIGADCABAIAuAYAIaQEQAC6BgAhvQRAALoGACHDBAIAuAYAIcUECAC-BgAhxgQIAL4GACHHBAAAvwYAIMgECAC-BgAhyQQIAL4GACHKBAgAvgYAIcsECAC-BgAhzAQIAL4GACHNBAgAvgYAIc4EAQC5BgAhzwRAAMMGACEIhQRAAAAAAYYEQAAAAAWHBEAAAAAFiARAAAAAAYkEQAAAAAGKBEAAAAABiwRAAAAAAY8EQACkBgAhF4gCAADIBgAgigIAAMkGACCLAgAAygYAIP0DAADEBgAw_gMAAKsDABD_AwAAxAYAMIAEAgC4BgAhhAQBALkGACGkBEAAugYAIdAEAQC5BgAh0QQBALkGACHSBAEAuQYAIdMEAQDFBgAh1AQCALgGACHVBAIAuAYAIdYEAgC4BgAh1wQCALgGACHYBAIAuAYAIdkEAgC4BgAh2gQCAMYGACHbBEAAwwYAIdwEIADHBgAh3QRAAMMGACELhQQBAAAAAYYEAQAAAAWHBAEAAAAFiAQBAAAAAYkEAQAAAAGKBAEAAAABiwQBAAAAAYwEAQAAAAGNBAEAAAABjgQBAAAAAY8EAQCRBgAhCIUEAgAAAAGGBAIAAAAFhwQCAAAABYgEAgAAAAGJBAIAAAABigQCAAAAAYsEAgAAAAGPBAIAkAYAIQKFBCAAAAABjwQgAKEGACEKjAIAALsGACD9AwAAtwYAMP4DAAC7AwAQ_wMAALcGADCABAIAuAYAIYQEAQC5BgAhpARAALoGACHRBAEAuQYAIaYFAAC7AwAgpwUAALsDACAD3gQAAK8DACDfBAAArwMAIOAEAACvAwAgA94EAACzAwAg3wQAALMDACDgBAAAswMAIAn9AwAAywYAMP4DAACmAwAQ_wMAAMsGADCABAIAjAYAIZAEAQCNBgAhmwQBAI0GACGlBEAAnQYAIeMEAQCNBgAh5AQBAI0GACEJ_QMAAMwGADD-AwAAkwMAEP8DAADMBgAwgAQCALgGACGQBAEAuQYAIZsEAQC5BgAhpQRAALoGACHjBAEAuQYAIeQEAQC5BgAhCv0DAADNBgAw_gMAAI0DABD_AwAAzQYAMIAEAgCMBgAhpARAAJ0GACG9BEAAnQYAIb8EAQCNBgAh5QQBAI0GACHmBAEAjQYAIecECADOBgAhDQYAAJMGACAwAACWBgAgMQAAlgYAIDIAAJYGACAzAACWBgAghQQIAAAAAYYECAAAAASHBAgAAAAEiAQIAAAAAYkECAAAAAGKBAgAAAABiwQIAAAAAY8ECADPBgAhDQYAAJMGACAwAACWBgAgMQAAlgYAIDIAAJYGACAzAACWBgAghQQIAAAAAYYECAAAAASHBAgAAAAEiAQIAAAAAYkECAAAAAGKBAgAAAABiwQIAAAAAY8ECADPBgAhCv0DAADQBgAw_gMAAPoCABD_AwAA0AYAMIAEAgC4BgAhpARAALoGACG9BEAAugYAIb8EAQC5BgAh5QQBALkGACHmBAEAuQYAIecECADRBgAhCIUECAAAAAGGBAgAAAAEhwQIAAAABIgECAAAAAGJBAgAAAABigQIAAAAAYsECAAAAAGPBAgAlgYAIQO9BEAAAAAB5QQBAAAAAeYEAQAAAAEK_QMAANMGADD-AwAA9AIAEP8DAADTBgAwgAQCAIwGACGkBEAAnQYAIaUEQACdBgAh6QQBAI0GACHqBAEAjgYAIesEQACdBgAh7AQAAKoGACAK_QMAANQGADD-AwAA4QIAEP8DAADUBgAwgAQCALgGACGkBEAAugYAIaUEQAC6BgAh6QQBALkGACHqBAEAxQYAIesEQAC6BgAh7AQAAL8GACAI_QMAANUGADD-AwAA2wIAEP8DAADVBgAwgAQCAIwGACGkBEAAnQYAIaUEQACdBgAh7QQBAI0GACHuBAIAjAYAIQj9AwAA1gYAMP4DAADIAgAQ_wMAANYGADCABAIAuAYAIaQEQAC6BgAhpQRAALoGACHtBAEAuQYAIe4EAgC4BgAhDP0DAADXBgAw_gMAAMICABD_AwAA1wYAMIAEAgCMBgAhhAQBAI0GACGbBAEAjQYAIaQEQACdBgAhpQRAAJ0GACHvBAEAjQYAIfAEAgCMBgAh8QQBAI0GACHyBEAAnQYAIQz9AwAA2AYAMP4DAACvAgAQ_wMAANgGADCABAIAuAYAIYQEAQC5BgAhmwQBALkGACGkBEAAugYAIaUEQAC6BgAh7wQBALkGACHwBAIAuAYAIfEEAQC5BgAh8gRAALoGACEP_QMAANkGADD-AwAAqQIAEP8DAADZBgAwgAQCAIwGACGBBAEAjQYAIaQEQACdBgAhwARAAJ0GACHzBAgAzgYAIfQEBADaBgAh9QQIAM4GACH2BAQA2gYAIfcEBADaBgAh-AQEANoGACH5BAIAjAYAIfoEAgCMBgAhDQYAAJMGACAwAACWBgAgMQAA3AYAIDIAANwGACAzAADcBgAghQQEAAAAAYYEBAAAAASHBAQAAAAEiAQEAAAAAYkEBAAAAAGKBAQAAAABiwQEAAAAAY8EBADbBgAhDQYAAJMGACAwAACWBgAgMQAA3AYAIDIAANwGACAzAADcBgAghQQEAAAAAYYEBAAAAASHBAQAAAAEiAQEAAAAAYkEBAAAAAGKBAQAAAABiwQEAAAAAY8EBADbBgAhCIUEBAAAAAGGBAQAAAAEhwQEAAAABIgEBAAAAAGJBAQAAAABigQEAAAAAYsEBAAAAAGPBAQA3AYAIQ_9AwAA3QYAMP4DAACWAgAQ_wMAAN0GADCABAIAuAYAIYEEAQC5BgAhpARAALoGACHABEAAugYAIfMECADRBgAh9AQEAN4GACH1BAgA0QYAIfYEBADeBgAh9wQEAN4GACH4BAQA3gYAIfkEAgC4BgAh-gQCALgGACEIhQQEAAAAAYYEBAAAAASHBAQAAAAEiAQEAAAAAYkEBAAAAAGKBAQAAAABiwQEAAAAAY8EBADcBgAhAoEEAQAAAAHABEAAAAABHP0DAADgBgAw_gMAAJACABD_AwAA4AYAMIAEAgCMBgAhgQQBAI0GACGEBAEAjQYAIZsEAQCNBgAhpARAAJ0GACGlBEAAnQYAIfcEBADaBgAh-AQEANoGACH5BAIAjAYAIfoEAgCMBgAh_AQBAI0GACH9BAEAjQYAIf4EAQCNBgAh_wQEANoGACGABQQA2gYAIYEFAgCMBgAhggUIAM4GACGDBQQA2gYAIYQFBADaBgAhhQUIAM4GACGGBQgAzgYAIYcFCADOBgAhiAUEANoGACGJBQQA2gYAIYoFQACdBgAhHP0DAADhBgAw_gMAAP0BABD_AwAA4QYAMIAEAgC4BgAhgQQBALkGACGEBAEAuQYAIZsEAQC5BgAhpARAALoGACGlBEAAugYAIfcEBADeBgAh-AQEAN4GACH5BAIAuAYAIfoEAgC4BgAh_AQBALkGACH9BAEAuQYAIf4EAQC5BgAh_wQEAN4GACGABQQA3gYAIYEFAgC4BgAhggUIANEGACGDBQQA3gYAIYQFBADeBgAhhQUIANEGACGGBQgA0QYAIYcFCADRBgAhiAUEAN4GACGJBQQA3gYAIYoFQAC6BgAhDf0DAADiBgAw_gMAAPcBABD_AwAA4gYAMIAEAgCMBgAhpARAAJ0GACHABAEAjQYAIYsFAQCNBgAhjAUIAM4GACGNBQgAzgYAIY4FAQCNBgAhjwUBAI0GACGQBQgAzgYAIZEFCADOBgAhDf0DAADjBgAw_gMAAOQBABD_AwAA4wYAMIAEAgC4BgAhpARAALoGACHABAEAuQYAIYsFAQC5BgAhjAUIANEGACGNBQgA0QYAIY4FAQC5BgAhjwUBALkGACGQBQgA0QYAIZEFCADRBgAhDP0DAADkBgAw_gMAAN4BABD_AwAA5AYAMIAEAgCMBgAhlwQBAI0GACGkBEAAnQYAIb8EAQCNBgAh7wQBAI0GACGSBQEAjQYAIZMFAQCOBgAhlAVAAJ0GACGVBQIAjAYAIQz9AwAA5QYAMP4DAADLAQAQ_wMAAOUGADCABAIAuAYAIZcEAQC5BgAhpARAALoGACG_BAEAuQYAIe8EAQC5BgAhkgUBALkGACGTBQEAxQYAIZQFQAC6BgAhlQUCALgGACEClwQBAAAAAZQFQAAAAAEJ_QMAAOcGADD-AwAAxQEAEP8DAADnBgAwgAQCAIwGACGkBEAAnQYAIaUEQACdBgAhwAQBAI0GACGXBQIAjAYAIZgFCADOBgAhDf0DAADoBgAw_gMAAK8BABD_AwAA6AYAMIAEAgCMBgAhhAQBAI0GACGXBAEAjQYAIaQEQACdBgAhpQRAAJ0GACG_BAEAjQYAIZkFAQCNBgAhmgUBAI0GACGbBQEAjQYAIZwFIACcBgAhDlcAAOoGACD9AwAA6QYAMP4DAACcAQAQ_wMAAOkGADCABAIAuAYAIYQEAQC5BgAhlwQBALkGACGkBEAAugYAIaUEQAC6BgAhvwQBALkGACGZBQEAuQYAIZoFAQC5BgAhmwUBALkGACGcBSAAxwYAIQPeBAAAlgEAIN8EAACWAQAg4AQAAJYBACACwAQBAAAAAZcFAgAAAAEKVgAA7QYAIP0DAADsBgAw_gMAAJYBABD_AwAA7AYAMIAEAgC4BgAhpARAALoGACGlBEAAugYAIcAEAQC5BgAhlwUCALgGACGYBQgA0QYAIRBXAADqBgAg_QMAAOkGADD-AwAAnAEAEP8DAADpBgAwgAQCALgGACGEBAEAuQYAIZcEAQC5BgAhpARAALoGACGlBEAAugYAIb8EAQC5BgAhmQUBALkGACGaBQEAuQYAIZsFAQC5BgAhnAUgAMcGACGmBQAAnAEAIKcFAACcAQAgCSMAAJsGACD9AwAA7gYAMP4DAACRAQAQ_wMAAO4GADCABAIAjAYAIYQEAQCNBgAhmAQBAI0GACGkBEAAnQYAIaUEQACdBgAhCSMAAPAGACD9AwAA7wYAMP4DAAB-ABD_AwAA7wYAMIAEAgC4BgAhhAQBALkGACGYBAEAuQYAIaQEQAC6BgAhpQRAALoGACEMhQSAAAAAAYgEgAAAAAGJBIAAAAABigSAAAAAAYsEgAAAAAGPBIAAAAABpwQBAAAAAagEAQAAAAGpBAEAAAABqgSAAAAAAasEgAAAAAGsBIAAAAABCv0DAADxBgAw_gMAAHgAEP8DAADxBgAwgAQCAIwGACGXBAEAjQYAIaQEQACdBgAhpQRAAJ0GACG9BAEAjQYAIZMFAQCNBgAhngUBAI0GACEK_QMAAPIGADD-AwAAZQAQ_wMAAPIGADCABAIAuAYAIZcEAQC5BgAhpARAALoGACGlBEAAugYAIb0EAQC5BgAhkwUBALkGACGeBQEAuQYAIQn9AwAA8wYAMP4DAABfABD_AwAA8wYAMIAEAgCMBgAhhAQBAI0GACGkBEAAnQYAIaUEQACdBgAhnwUBAI0GACGgBQEAjQYAIQkVAAD1BgAg_QMAAPQGADD-AwAAMwAQ_wMAAPQGADCABAIAuAYAIYEEAQC5BgAhggQBAMUGACGDBAIAuAYAIYQEAQC5BgAhJQ4AAPsGACAPAACABwAgEAAA_AYAIBEAAP0GACASAAD-BgAgEwAA_wYAIBQAAPgGACAWAACBBwAg_QMAAPoGADD-AwAAKAAQ_wMAAPoGADCABAIAuAYAIYEEAQC5BgAhkQQBAMUGACGSBAEAxQYAIZMEAQDFBgAhlAQCALgGACGVBAIAuAYAIZYEAgDGBgAhlwQBALkGACGYBAEAuQYAIZkEAQDFBgAhmgRAAMMGACGbBAEAuQYAIZwEQADDBgAhnQQCAMYGACGeBAIAuAYAIZ8EAADwBgAgoAQCALgGACGhBCAAxwYAIaIEQADDBgAhowQBAMUGACGkBEAAugYAIaUEQAC6BgAhpgQCAMYGACGmBQAAKAAgpwUAACgAIAKDBAIAAAABkAQCAAAAAQoDAAD4BgAgFQAA9QYAIP0DAAD3BgAw_gMAAC8AEP8DAAD3BgAwgAQCALgGACGBBAEAuQYAIYIEAQDFBgAhgwQCALgGACGQBAIAuAYAIRQHAACKBwAgCAAAiwcAIAsAAIwHACAMAACNBwAgGQAAjgcAIBoAAIcHACAbAAD-BgAgHAAA_gYAIB0AAIAHACD9AwAAiQcAMP4DAAAaABD_AwAAiQcAMIAEAgC4BgAhhAQBALkGACGkBEAAugYAIaUEQAC6BgAhnwUBALkGACGgBQEAuQYAIaYFAAAaACCnBQAAGgAgApQEAgAAAAGmBAIAAAABIw4AAPsGACAPAACABwAgEAAA_AYAIBEAAP0GACASAAD-BgAgEwAA_wYAIBQAAPgGACAWAACBBwAg_QMAAPoGADD-AwAAKAAQ_wMAAPoGADCABAIAuAYAIYEEAQC5BgAhkQQBAMUGACGSBAEAxQYAIZMEAQDFBgAhlAQCALgGACGVBAIAuAYAIZYEAgDGBgAhlwQBALkGACGYBAEAuQYAIZkEAQDFBgAhmgRAAMMGACGbBAEAuQYAIZwEQADDBgAhnQQCAMYGACGeBAIAuAYAIZ8EAADwBgAgoAQCALgGACGhBCAAxwYAIaIEQADDBgAhowQBAMUGACGkBEAAugYAIaUEQAC6BgAhpgQCAMYGACEWDQAA-AYAIA8AAIcHACAXAAD-BgAgGAAAiAcAIP0DAACGBwAw_gMAABwAEP8DAACGBwAwgAQCALgGACGBBAEAuQYAIYQEAQC5BgAhoQQgAMcGACGkBEAAugYAIaUEQAC6BgAhsAQBALkGACGxBAIAuAYAIbIEIADHBgAhswQAAPAGACC0BAAAvwYAILUEAQDFBgAhtgQBALkGACGmBQAAHAAgpwUAABwAIBEOAAD7BgAgFwAA_gYAIP0DAACDBwAw_gMAACQAEP8DAACDBwAwgAQCALgGACGBBAEAuQYAIYQEAQC5BgAhkQQBAMUGACGUBAIAuAYAIaAEAgC4BgAhpARAALoGACGlBEAAugYAIa0EAQDFBgAhrgQBALkGACGmBQAAJAAgpwUAACQAICUOAAD7BgAgDwAAgAcAIBAAAPwGACARAAD9BgAgEgAA_gYAIBMAAP8GACAUAAD4BgAgFgAAgQcAIP0DAAD6BgAw_gMAACgAEP8DAAD6BgAwgAQCALgGACGBBAEAuQYAIZEEAQDFBgAhkgQBAMUGACGTBAEAxQYAIZQEAgC4BgAhlQQCALgGACGWBAIAxgYAIZcEAQC5BgAhmAQBALkGACGZBAEAxQYAIZoEQADDBgAhmwQBALkGACGcBEAAwwYAIZ0EAgDGBgAhngQCALgGACGfBAAA8AYAIKAEAgC4BgAhoQQgAMcGACGiBEAAwwYAIaMEAQDFBgAhpARAALoGACGlBEAAugYAIaYEAgDGBgAhpgUAACgAIKcFAAAoACAD3gQAACgAIN8EAAAoACDgBAAAKAAgFAcAAIoHACAIAACLBwAgCwAAjAcAIAwAAI0HACAZAACOBwAgGgAAhwcAIBsAAP4GACAcAAD-BgAgHQAAgAcAIP0DAACJBwAw_gMAABoAEP8DAACJBwAwgAQCALgGACGEBAEAuQYAIaQEQAC6BgAhpQRAALoGACGfBQEAuQYAIaAFAQC5BgAhpgUAABoAIKcFAAAaACAD3gQAAC8AIN8EAAAvACDgBAAALwAgA94EAAAzACDfBAAAMwAg4AQAADMAIAKEBAEAAAABlAQCAAAAAQ8OAAD7BgAgFwAA_gYAIP0DAACDBwAw_gMAACQAEP8DAACDBwAwgAQCALgGACGBBAEAuQYAIYQEAQC5BgAhkQQBAMUGACGUBAIAuAYAIaAEAgC4BgAhpARAALoGACGlBEAAugYAIa0EAQDFBgAhrgQBALkGACECkAQCAAAAAZQEAgAAAAEMAwAA-AYAIA4AAPsGACD9AwAAhQcAMP4DAAAgABD_AwAAhQcAMIAEAgC4BgAhgQQBALkGACGQBAIAuAYAIZEEAQDFBgAhlAQCALgGACGkBEAAugYAIa8EAQC5BgAhFA0AAPgGACAPAACHBwAgFwAA_gYAIBgAAIgHACD9AwAAhgcAMP4DAAAcABD_AwAAhgcAMIAEAgC4BgAhgQQBALkGACGEBAEAuQYAIaEEIADHBgAhpARAALoGACGlBEAAugYAIbAEAQC5BgAhsQQCALgGACGyBCAAxwYAIbMEAADwBgAgtAQAAL8GACC1BAEAxQYAIbYEAQC5BgAhA94EAAAgACDfBAAAIAAg4AQAACAAIAPeBAAAJAAg3wQAACQAIOAEAAAkACASBwAAigcAIAgAAIsHACALAACMBwAgDAAAjQcAIBkAAI4HACAaAACHBwAgGwAA_gYAIBwAAP4GACAdAACABwAg_QMAAIkHADD-AwAAGgAQ_wMAAIkHADCABAIAuAYAIYQEAQC5BgAhpARAALoGACGlBEAAugYAIZ8FAQC5BgAhoAUBALkGACED3gQAAAMAIN8EAAADACDgBAAAAwAgA94EAAAHACDfBAAABwAg4AQAAAcAIAPeBAAADQAg3wQAAA0AIOAEAAANACAD3gQAABYAIN8EAAAWACDgBAAAFgAgA94EAAAcACDfBAAAHAAg4AQAABwAIAwDAAD_BgAgCgAA8AYAIP0DAACPBwAw_gMAABYAEP8DAACPBwAwgAQCALgGACGEBAEAuQYAIZAEAgDGBgAhpARAALoGACGlBEAAugYAIbcEAQC5BgAhuAQgAMcGACENCQAAkQcAIP0DAACQBwAw_gMAABEAEP8DAACQBwAwgAQCALgGACGXBAEAuQYAIZwEQADDBgAhoAQCALgGACGkBEAAugYAIaUEQAC6BgAhuQQCALgGACG6BAEAuQYAIbsEIADHBgAhDwMAAPgGACAKAACTBwAg_QMAAJIHADD-AwAADQAQ_wMAAJIHADCABAIAuAYAIZAEAgC4BgAhlwQBALkGACGhBCAAxwYAIaQEQAC6BgAhpQRAALoGACG3BAEAuQYAIboEAQC5BgAhpgUAAA0AIKcFAAANACANAwAA-AYAIAoAAJMHACD9AwAAkgcAMP4DAAANABD_AwAAkgcAMIAEAgC4BgAhkAQCALgGACGXBAEAuQYAIaEEIADHBgAhpARAALoGACGlBEAAugYAIbcEAQC5BgAhugQBALkGACED3gQAABEAIN8EAAARACDgBAAAEQAgA7wEAgAAAAG9BAEAAAABvgQCAAAAAQ0DAAD4BgAgBAAAlgcAIP0DAACVBwAw_gMAAAcAEP8DAACVBwAwgAQCALgGACGQBAIAuAYAIaQEQAC6BgAhugQBALkGACG8BAIAuAYAIb0EAQC5BgAhvgQCALgGACG_BAEAuQYAIRMDAAD4BgAgBQAAiwcAIP0DAACXBwAw_gMAAAMAEP8DAACXBwAwgAQCALgGACGEBAEAuQYAIZAEAgC4BgAhoAQCALgGACGhBCAAxwYAIaQEQAC6BgAhpQRAALoGACG1BAEAuQYAIboEAQC5BgAhwAQBALkGACHBBAIAuAYAIcIEAQC5BgAhpgUAAAMAIKcFAAADACARAwAA-AYAIAUAAIsHACD9AwAAlwcAMP4DAAADABD_AwAAlwcAMIAEAgC4BgAhhAQBALkGACGQBAIAuAYAIaAEAgC4BgAhoQQgAMcGACGkBEAAugYAIaUEQAC6BgAhtQQBALkGACG6BAEAuQYAIcAEAQC5BgAhwQQCALgGACHCBAEAuQYAIQAAAAAAAAGrBQEAAAABAasFAQAAAAEFqwUCAAAAAbEFAgAAAAGyBQIAAAABswUCAAAAAbQFAgAAAAEFKgAAlgwAICsAAJkMACCoBQAAlwwAIKkFAACYDAAgrgUAACoAIAMqAACWDAAgqAUAAJcMACCuBQAAKgAgAAAAAAAFKgAAjgwAICsAAJQMACCoBQAAjwwAIKkFAACTDAAgrgUAACoAIAUqAACMDAAgKwAAkQwAIKgFAACNDAAgqQUAAJAMACCuBQAAAQAgAyoAAI4MACCoBQAAjwwAIK4FAAAqACADKgAAjAwAIKgFAACNDAAgrgUAAAEAIAAAAAAAAasFQAAAAAEBqwUgAAAAAQGrBUAAAAABBasFAgAAAAGxBQIAAAABsgUCAAAAAbMFAgAAAAG0BQIAAAABBSoAAPgLACArAACKDAAgqAUAAPkLACCpBQAAiQwAIK4FAAAeACAFKgAA9gsAICsAAIcMACCoBQAA9wsAIKkFAACGDAAgrgUAACYAIAcqAADwCwAgKwAAhAwAIKgFAADxCwAgqQUAAIMMACCsBQAAKAAgrQUAACgAIK4FAAAqACALKgAA1QcAMCsAANoHADCoBQAA1gcAMKkFAADXBwAwqgUAANgHACCrBQAA2QcAMKwFAADZBwAwrQUAANkHADCuBQAA2QcAMK8FAADbBwAwsAUAANwHADAHKgAA9AsAICsAAIEMACCoBQAA9QsAIKkFAACADAAgrAUAABoAIK0FAAAaACCuBQAAAQAgBSoAAPILACArAAD-CwAgqAUAAPMLACCpBQAA_QsAIK4FAAABACALKgAAyQcAMCsAAM4HADCoBQAAygcAMKkFAADLBwAwqgUAAMwHACCrBQAAzQcAMKwFAADNBwAwrQUAAM0HADCuBQAAzQcAMK8FAADPBwAwsAUAANAHADALKgAAvQcAMCsAAMIHADCoBQAAvgcAMKkFAAC_BwAwqgUAAMAHACCrBQAAwQcAMKwFAADBBwAwrQUAAMEHADCuBQAAwQcAMK8FAADDBwAwsAUAAMQHADAEgAQCAAAAAYEEAQAAAAGCBAEAAAABhAQBAAAAAQIAAAA1ACAqAADIBwAgAwAAADUAICoAAMgHACArAADHBwAgASMAAPwLADAJFQAA9QYAIP0DAAD0BgAw_gMAADMAEP8DAAD0BgAwgAQCAAAAAYEEAQAAAAGCBAEAxQYAIYMEAgC4BgAhhAQBALkGACECAAAANQAgIwAAxwcAIAIAAADFBwAgIwAAxgcAIAj9AwAAxAcAMP4DAADFBwAQ_wMAAMQHADCABAIAuAYAIYEEAQC5BgAhggQBAMUGACGDBAIAuAYAIYQEAQC5BgAhCP0DAADEBwAw_gMAAMUHABD_AwAAxAcAMIAEAgC4BgAhgQQBALkGACGCBAEAxQYAIYMEAgC4BgAhhAQBALkGACEEgAQCAKAHACGBBAEAngcAIYIEAQCfBwAhhAQBAJ4HACEEgAQCAKAHACGBBAEAngcAIYIEAQCfBwAhhAQBAJ4HACEEgAQCAAAAAYEEAQAAAAGCBAEAAAABhAQBAAAAAQUDAACrBwAggAQCAAAAAYEEAQAAAAGCBAEAAAABkAQCAAAAAQIAAAAxACAqAADUBwAgAwAAADEAICoAANQHACArAADTBwAgASMAAPsLADALAwAA-AYAIBUAAPUGACD9AwAA9wYAMP4DAAAvABD_AwAA9wYAMIAEAgAAAAGBBAEAAAABggQBAMUGACGDBAIAuAYAIZAEAgC4BgAhoQUAAPYGACACAAAAMQAgIwAA0wcAIAIAAADRBwAgIwAA0gcAIAj9AwAA0AcAMP4DAADRBwAQ_wMAANAHADCABAIAuAYAIYEEAQC5BgAhggQBAMUGACGDBAIAuAYAIZAEAgC4BgAhCP0DAADQBwAw_gMAANEHABD_AwAA0AcAMIAEAgC4BgAhgQQBALkGACGCBAEAxQYAIYMEAgC4BgAhkAQCALgGACEEgAQCAKAHACGBBAEAngcAIYIEAQCfBwAhkAQCAKAHACEFAwAAqQcAIIAEAgCgBwAhgQQBAJ4HACGCBAEAnwcAIZAEAgCgBwAhBQMAAKsHACCABAIAAAABgQQBAAAAAYIEAQAAAAGQBAIAAAABHg4AAOEHACAPAADmBwAgEAAA4gcAIBIAAOMHACATAADkBwAgFAAA5QcAIBYAAOcHACCABAIAAAABgQQBAAAAAZEEAQAAAAGSBAEAAAABkwQBAAAAAZQEAgAAAAGVBAIAAAABlwQBAAAAAZgEAQAAAAGZBAEAAAABmgRAAAAAAZsEAQAAAAGcBEAAAAABnQQCAAAAAZ4EAgAAAAGfBIAAAAABoAQCAAAAAaEEIAAAAAGiBEAAAAABowQBAAAAAaQEQAAAAAGlBEAAAAABpgQCAAAAAQIAAAAqACAqAADgBwAgAwAAACoAICoAAOAHACArAADfBwAgASMAAPoLADAkDgAA-wYAIA8AAIAHACAQAAD8BgAgEQAA_QYAIBIAAP4GACATAAD_BgAgFAAA-AYAIBYAAIEHACD9AwAA-gYAMP4DAAAoABD_AwAA-gYAMIAEAgAAAAGBBAEAAAABkQQBAMUGACGSBAEAxQYAIZMEAQDFBgAhlAQCALgGACGVBAIAuAYAIZYEAgDGBgAhlwQBALkGACGYBAEAuQYAIZkEAQDFBgAhmgRAAMMGACGbBAEAuQYAIZwEQADDBgAhnQQCAMYGACGeBAIAuAYAIZ8EAADwBgAgoAQCALgGACGhBCAAxwYAIaIEQADDBgAhowQBAMUGACGkBEAAugYAIaUEQAC6BgAhpgQCAMYGACGiBQAA-QYAIAIAAAAqACAjAADfBwAgAgAAAN0HACAjAADeBwAgG_0DAADcBwAw_gMAAN0HABD_AwAA3AcAMIAEAgC4BgAhgQQBALkGACGRBAEAxQYAIZIEAQDFBgAhkwQBAMUGACGUBAIAuAYAIZUEAgC4BgAhlgQCAMYGACGXBAEAuQYAIZgEAQC5BgAhmQQBAMUGACGaBEAAwwYAIZsEAQC5BgAhnARAAMMGACGdBAIAxgYAIZ4EAgC4BgAhnwQAAPAGACCgBAIAuAYAIaEEIADHBgAhogRAAMMGACGjBAEAxQYAIaQEQAC6BgAhpQRAALoGACGmBAIAxgYAIRv9AwAA3AcAMP4DAADdBwAQ_wMAANwHADCABAIAuAYAIYEEAQC5BgAhkQQBAMUGACGSBAEAxQYAIZMEAQDFBgAhlAQCALgGACGVBAIAuAYAIZYEAgDGBgAhlwQBALkGACGYBAEAuQYAIZkEAQDFBgAhmgRAAMMGACGbBAEAuQYAIZwEQADDBgAhnQQCAMYGACGeBAIAuAYAIZ8EAADwBgAgoAQCALgGACGhBCAAxwYAIaIEQADDBgAhowQBAMUGACGkBEAAugYAIaUEQAC6BgAhpgQCAMYGACEXgAQCAKAHACGBBAEAngcAIZEEAQCfBwAhkgQBAJ8HACGTBAEAnwcAIZQEAgCgBwAhlQQCAKAHACGXBAEAngcAIZgEAQCeBwAhmQQBAJ8HACGaBEAAsQcAIZsEAQCeBwAhnARAALEHACGdBAIAtAcAIZ4EAgCgBwAhnwSAAAAAAaAEAgCgBwAhoQQgALIHACGiBEAAsQcAIaMEAQCfBwAhpARAALMHACGlBEAAswcAIaYEAgC0BwAhHg4AALUHACAPAAC7BwAgEAAAtgcAIBIAALgHACATAAC5BwAgFAAAugcAIBYAALwHACCABAIAoAcAIYEEAQCeBwAhkQQBAJ8HACGSBAEAnwcAIZMEAQCfBwAhlAQCAKAHACGVBAIAoAcAIZcEAQCeBwAhmAQBAJ4HACGZBAEAnwcAIZoEQACxBwAhmwQBAJ4HACGcBEAAsQcAIZ0EAgC0BwAhngQCAKAHACGfBIAAAAABoAQCAKAHACGhBCAAsgcAIaIEQACxBwAhowQBAJ8HACGkBEAAswcAIaUEQACzBwAhpgQCALQHACEeDgAA4QcAIA8AAOYHACAQAADiBwAgEgAA4wcAIBMAAOQHACAUAADlBwAgFgAA5wcAIIAEAgAAAAGBBAEAAAABkQQBAAAAAZIEAQAAAAGTBAEAAAABlAQCAAAAAZUEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGdBAIAAAABngQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABAyoAAPgLACCoBQAA-QsAIK4FAAAeACADKgAA9gsAIKgFAAD3CwAgrgUAACYAIAQqAADVBwAwqAUAANYHADCqBQAA2AcAIK4FAADZBwAwAyoAAPQLACCoBQAA9QsAIK4FAAABACADKgAA8gsAIKgFAADzCwAgrgUAAAEAIAQqAADJBwAwqAUAAMoHADCqBQAAzAcAIK4FAADNBwAwBCoAAL0HADCoBQAAvgcAMKoFAADABwAgrgUAAMEHADADKgAA8AsAIKgFAADxCwAgrgUAACoAIAAAAAAABSoAAOoLACArAADuCwAgqAUAAOsLACCpBQAA7QsAIK4FAAAeACALKgAA8AcAMCsAAPQHADCoBQAA8QcAMKkFAADyBwAwqgUAAPMHACCrBQAA2QcAMKwFAADZBwAwrQUAANkHADCuBQAA2QcAMK8FAAD1BwAwsAUAANwHADAeDgAA4QcAIA8AAOYHACARAADoBwAgEgAA4wcAIBMAAOQHACAUAADlBwAgFgAA5wcAIIAEAgAAAAGBBAEAAAABkQQBAAAAAZIEAQAAAAGTBAEAAAABlAQCAAAAAZYEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGdBAIAAAABngQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABAgAAACoAICoAAPgHACADAAAAKgAgKgAA-AcAICsAAPcHACABIwAA7AsAMAIAAAAqACAjAAD3BwAgAgAAAN0HACAjAAD2BwAgF4AEAgCgBwAhgQQBAJ4HACGRBAEAnwcAIZIEAQCfBwAhkwQBAJ8HACGUBAIAoAcAIZYEAgC0BwAhlwQBAJ4HACGYBAEAngcAIZkEAQCfBwAhmgRAALEHACGbBAEAngcAIZwEQACxBwAhnQQCALQHACGeBAIAoAcAIZ8EgAAAAAGgBAIAoAcAIaEEIACyBwAhogRAALEHACGjBAEAnwcAIaQEQACzBwAhpQRAALMHACGmBAIAtAcAIR4OAAC1BwAgDwAAuwcAIBEAALcHACASAAC4BwAgEwAAuQcAIBQAALoHACAWAAC8BwAggAQCAKAHACGBBAEAngcAIZEEAQCfBwAhkgQBAJ8HACGTBAEAnwcAIZQEAgCgBwAhlgQCALQHACGXBAEAngcAIZgEAQCeBwAhmQQBAJ8HACGaBEAAsQcAIZsEAQCeBwAhnARAALEHACGdBAIAtAcAIZ4EAgCgBwAhnwSAAAAAAaAEAgCgBwAhoQQgALIHACGiBEAAsQcAIaMEAQCfBwAhpARAALMHACGlBEAAswcAIaYEAgC0BwAhHg4AAOEHACAPAADmBwAgEQAA6AcAIBIAAOMHACATAADkBwAgFAAA5QcAIBYAAOcHACCABAIAAAABgQQBAAAAAZEEAQAAAAGSBAEAAAABkwQBAAAAAZQEAgAAAAGWBAIAAAABlwQBAAAAAZgEAQAAAAGZBAEAAAABmgRAAAAAAZsEAQAAAAGcBEAAAAABnQQCAAAAAZ4EAgAAAAGfBIAAAAABoAQCAAAAAaEEIAAAAAGiBEAAAAABowQBAAAAAaQEQAAAAAGlBEAAAAABpgQCAAAAAQMqAADqCwAgqAUAAOsLACCuBQAAHgAgBCoAAPAHADCoBQAA8QcAMKoFAADzBwAgrgUAANkHADAAAAAAAAUqAADiCwAgKwAA6AsAIKgFAADjCwAgqQUAAOcLACCuBQAAHgAgBSoAAOALACArAADlCwAgqAUAAOELACCpBQAA5AsAIK4FAAABACADKgAA4gsAIKgFAADjCwAgrgUAAB4AIAMqAADgCwAgqAUAAOELACCuBQAAAQAgAAAAAAAFKgAA2AsAICsAAN4LACCoBQAA2QsAIKkFAADdCwAgrgUAAAEAIAsqAACiCAAwKwAApwgAMKgFAACjCAAwqQUAAKQIADCqBQAApQgAIKsFAACmCAAwrAUAAKYIADCtBQAApggAMK4FAACmCAAwrwUAAKgIADCwBQAAqQgAMAsqAACWCAAwKwAAmwgAMKgFAACXCAAwqQUAAJgIADCqBQAAmQgAIKsFAACaCAAwrAUAAJoIADCtBQAAmggAMK4FAACaCAAwrwUAAJwIADCwBQAAnQgAMAsqAACNCAAwKwAAkQgAMKgFAACOCAAwqQUAAI8IADCqBQAAkAgAIKsFAADZBwAwrAUAANkHADCtBQAA2QcAMK4FAADZBwAwrwUAAJIIADCwBQAA3AcAMB4PAADmBwAgEAAA4gcAIBEAAOgHACASAADjBwAgEwAA5AcAIBQAAOUHACAWAADnBwAggAQCAAAAAYEEAQAAAAGRBAEAAAABkgQBAAAAAZMEAQAAAAGVBAIAAAABlgQCAAAAAZcEAQAAAAGYBAEAAAABmQQBAAAAAZoEQAAAAAGbBAEAAAABnARAAAAAAZ0EAgAAAAGeBAIAAAABnwSAAAAAAaAEAgAAAAGhBCAAAAABogRAAAAAAaMEAQAAAAGkBEAAAAABpQRAAAAAAaYEAgAAAAECAAAAKgAgKgAAlQgAIAMAAAAqACAqAACVCAAgKwAAlAgAIAEjAADcCwAwAgAAACoAICMAAJQIACACAAAA3QcAICMAAJMIACAXgAQCAKAHACGBBAEAngcAIZEEAQCfBwAhkgQBAJ8HACGTBAEAnwcAIZUEAgCgBwAhlgQCALQHACGXBAEAngcAIZgEAQCeBwAhmQQBAJ8HACGaBEAAsQcAIZsEAQCeBwAhnARAALEHACGdBAIAtAcAIZ4EAgCgBwAhnwSAAAAAAaAEAgCgBwAhoQQgALIHACGiBEAAsQcAIaMEAQCfBwAhpARAALMHACGlBEAAswcAIaYEAgC0BwAhHg8AALsHACAQAAC2BwAgEQAAtwcAIBIAALgHACATAAC5BwAgFAAAugcAIBYAALwHACCABAIAoAcAIYEEAQCeBwAhkQQBAJ8HACGSBAEAnwcAIZMEAQCfBwAhlQQCAKAHACGWBAIAtAcAIZcEAQCeBwAhmAQBAJ4HACGZBAEAnwcAIZoEQACxBwAhmwQBAJ4HACGcBEAAsQcAIZ0EAgC0BwAhngQCAKAHACGfBIAAAAABoAQCAKAHACGhBCAAsgcAIaIEQACxBwAhowQBAJ8HACGkBEAAswcAIaUEQACzBwAhpgQCALQHACEeDwAA5gcAIBAAAOIHACARAADoBwAgEgAA4wcAIBMAAOQHACAUAADlBwAgFgAA5wcAIIAEAgAAAAGBBAEAAAABkQQBAAAAAZIEAQAAAAGTBAEAAAABlQQCAAAAAZYEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGdBAIAAAABngQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABChcAAPoHACCABAIAAAABgQQBAAAAAYQEAQAAAAGRBAEAAAABoAQCAAAAAaQEQAAAAAGlBEAAAAABrQQBAAAAAa4EAQAAAAECAAAAJgAgKgAAoQgAIAMAAAAmACAqAAChCAAgKwAAoAgAIAEjAADbCwAwEA4AAPsGACAXAAD-BgAg_QMAAIMHADD-AwAAJAAQ_wMAAIMHADCABAIAAAABgQQBAAAAAYQEAQC5BgAhkQQBAMUGACGUBAIAuAYAIaAEAgC4BgAhpARAALoGACGlBEAAugYAIa0EAQDFBgAhrgQBALkGACGjBQAAggcAIAIAAAAmACAjAACgCAAgAgAAAJ4IACAjAACfCAAgDf0DAACdCAAw_gMAAJ4IABD_AwAAnQgAMIAEAgC4BgAhgQQBALkGACGEBAEAuQYAIZEEAQDFBgAhlAQCALgGACGgBAIAuAYAIaQEQAC6BgAhpQRAALoGACGtBAEAxQYAIa4EAQC5BgAhDf0DAACdCAAw_gMAAJ4IABD_AwAAnQgAMIAEAgC4BgAhgQQBALkGACGEBAEAuQYAIZEEAQDFBgAhlAQCALgGACGgBAIAuAYAIaQEQAC6BgAhpQRAALoGACGtBAEAxQYAIa4EAQC5BgAhCYAEAgCgBwAhgQQBAJ4HACGEBAEAngcAIZEEAQCfBwAhoAQCAKAHACGkBEAAswcAIaUEQACzBwAhrQQBAJ8HACGuBAEAngcAIQoXAADvBwAggAQCAKAHACGBBAEAngcAIYQEAQCeBwAhkQQBAJ8HACGgBAIAoAcAIaQEQACzBwAhpQRAALMHACGtBAEAnwcAIa4EAQCeBwAhChcAAPoHACCABAIAAAABgQQBAAAAAYQEAQAAAAGRBAEAAAABoAQCAAAAAaQEQAAAAAGlBEAAAAABrQQBAAAAAa4EAQAAAAEHAwAAgwgAIIAEAgAAAAGBBAEAAAABkAQCAAAAAZEEAQAAAAGkBEAAAAABrwQBAAAAAQIAAAAiACAqAACtCAAgAwAAACIAICoAAK0IACArAACsCAAgASMAANoLADANAwAA-AYAIA4AAPsGACD9AwAAhQcAMP4DAAAgABD_AwAAhQcAMIAEAgAAAAGBBAEAAAABkAQCALgGACGRBAEAxQYAIZQEAgC4BgAhpARAALoGACGvBAEAuQYAIaQFAACEBwAgAgAAACIAICMAAKwIACACAAAAqggAICMAAKsIACAK_QMAAKkIADD-AwAAqggAEP8DAACpCAAwgAQCALgGACGBBAEAuQYAIZAEAgC4BgAhkQQBAMUGACGUBAIAuAYAIaQEQAC6BgAhrwQBALkGACEK_QMAAKkIADD-AwAAqggAEP8DAACpCAAwgAQCALgGACGBBAEAuQYAIZAEAgC4BgAhkQQBAMUGACGUBAIAuAYAIaQEQAC6BgAhrwQBALkGACEGgAQCAKAHACGBBAEAngcAIZAEAgCgBwAhkQQBAJ8HACGkBEAAswcAIa8EAQCeBwAhBwMAAIEIACCABAIAoAcAIYEEAQCeBwAhkAQCAKAHACGRBAEAnwcAIaQEQACzBwAhrwQBAJ4HACEHAwAAgwgAIIAEAgAAAAGBBAEAAAABkAQCAAAAAZEEAQAAAAGkBEAAAAABrwQBAAAAAQMqAADYCwAgqAUAANkLACCuBQAAAQAgBCoAAKIIADCoBQAAowgAMKoFAAClCAAgrgUAAKYIADAEKgAAlggAMKgFAACXCAAwqgUAAJkIACCuBQAAmggAMAQqAACNCAAwqAUAAI4IADCqBQAAkAgAIK4FAADZBwAwAAAAAAAHKgAA0wsAICsAANYLACCoBQAA1AsAIKkFAADVCwAgrAUAABoAIK0FAAAaACCuBQAAAQAgAyoAANMLACCoBQAA1AsAIK4FAAABACAAAAAAAAUqAADOCwAgKwAA0QsAIKgFAADPCwAgqQUAANALACCuBQAADwAgAyoAAM4LACCoBQAAzwsAIK4FAAAPACAAAAAAAAUqAADICwAgKwAAzAsAIKgFAADJCwAgqQUAAMsLACCuBQAAAQAgCyoAAMcIADArAADMCAAwqAUAAMgIADCpBQAAyQgAMKoFAADKCAAgqwUAAMsIADCsBQAAywgAMK0FAADLCAAwrgUAAMsIADCvBQAAzQgAMLAFAADOCAAwCIAEAgAAAAGXBAEAAAABnARAAAAAAaAEAgAAAAGkBEAAAAABpQRAAAAAAboEAQAAAAG7BCAAAAABAgAAABMAICoAANIIACADAAAAEwAgKgAA0ggAICsAANEIACABIwAAygsAMA0JAACRBwAg_QMAAJAHADD-AwAAEQAQ_wMAAJAHADCABAIAAAABlwQBALkGACGcBEAAwwYAIaAEAgC4BgAhpARAALoGACGlBEAAugYAIbkEAgC4BgAhugQBALkGACG7BCAAxwYAIQIAAAATACAjAADRCAAgAgAAAM8IACAjAADQCAAgDP0DAADOCAAw_gMAAM8IABD_AwAAzggAMIAEAgC4BgAhlwQBALkGACGcBEAAwwYAIaAEAgC4BgAhpARAALoGACGlBEAAugYAIbkEAgC4BgAhugQBALkGACG7BCAAxwYAIQz9AwAAzggAMP4DAADPCAAQ_wMAAM4IADCABAIAuAYAIZcEAQC5BgAhnARAAMMGACGgBAIAuAYAIaQEQAC6BgAhpQRAALoGACG5BAIAuAYAIboEAQC5BgAhuwQgAMcGACEIgAQCAKAHACGXBAEAngcAIZwEQACxBwAhoAQCAKAHACGkBEAAswcAIaUEQACzBwAhugQBAJ4HACG7BCAAsgcAIQiABAIAoAcAIZcEAQCeBwAhnARAALEHACGgBAIAoAcAIaQEQACzBwAhpQRAALMHACG6BAEAngcAIbsEIACyBwAhCIAEAgAAAAGXBAEAAAABnARAAAAAAaAEAgAAAAGkBEAAAAABpQRAAAAAAboEAQAAAAG7BCAAAAABAyoAAMgLACCoBQAAyQsAIK4FAAABACAEKgAAxwgAMKgFAADICAAwqgUAAMoIACCuBQAAywgAMAAAAAAABSoAAMALACArAADGCwAgqAUAAMELACCpBQAAxQsAIK4FAAAFACAFKgAAvgsAICsAAMMLACCoBQAAvwsAIKkFAADCCwAgrgUAAAEAIAMqAADACwAgqAUAAMELACCuBQAABQAgAyoAAL4LACCoBQAAvwsAIK4FAAABACAAAAAAAAUqAAC4CwAgKwAAvAsAIKgFAAC5CwAgqQUAALsLACCuBQAAAQAgCyoAAOUIADArAADqCAAwqAUAAOYIADCpBQAA5wgAMKoFAADoCAAgqwUAAOkIADCsBQAA6QgAMK0FAADpCAAwrgUAAOkIADCvBQAA6wgAMLAFAADsCAAwCAMAAN0IACCABAIAAAABkAQCAAAAAaQEQAAAAAG6BAEAAAABvQQBAAAAAb4EAgAAAAG_BAEAAAABAgAAAAkAICoAAPAIACADAAAACQAgKgAA8AgAICsAAO8IACABIwAAugsAMA4DAAD4BgAgBAAAlgcAIP0DAACVBwAw_gMAAAcAEP8DAACVBwAwgAQCAAAAAZAEAgC4BgAhpARAALoGACG6BAEAuQYAIbwEAgC4BgAhvQQBALkGACG-BAIAuAYAIb8EAQC5BgAhpQUAAJQHACACAAAACQAgIwAA7wgAIAIAAADtCAAgIwAA7ggAIAv9AwAA7AgAMP4DAADtCAAQ_wMAAOwIADCABAIAuAYAIZAEAgC4BgAhpARAALoGACG6BAEAuQYAIbwEAgC4BgAhvQQBALkGACG-BAIAuAYAIb8EAQC5BgAhC_0DAADsCAAw_gMAAO0IABD_AwAA7AgAMIAEAgC4BgAhkAQCALgGACGkBEAAugYAIboEAQC5BgAhvAQCALgGACG9BAEAuQYAIb4EAgC4BgAhvwQBALkGACEHgAQCAKAHACGQBAIAoAcAIaQEQACzBwAhugQBAJ4HACG9BAEAngcAIb4EAgCgBwAhvwQBAJ4HACEIAwAA2wgAIIAEAgCgBwAhkAQCAKAHACGkBEAAswcAIboEAQCeBwAhvQQBAJ4HACG-BAIAoAcAIb8EAQCeBwAhCAMAAN0IACCABAIAAAABkAQCAAAAAaQEQAAAAAG6BAEAAAABvQQBAAAAAb4EAgAAAAG_BAEAAAABAyoAALgLACCoBQAAuQsAIK4FAAABACAEKgAA5QgAMKgFAADmCAAwqgUAAOgIACCuBQAA6QgAMAAAAAAABasFCAAAAAGxBQgAAAABsgUIAAAAAbMFCAAAAAG0BQgAAAABBSoAALMLACArAAC2CwAgqAUAALQLACCpBQAAtQsAIK4FAACtAwAgAyoAALMLACCoBQAAtAsAIK4FAACtAwAgAAAAAAAFKgAArgsAICsAALELACCoBQAArwsAIKkFAACwCwAgrgUAAK0DACADKgAArgsAIKgFAACvCwAgrgUAAK0DACAAAAAAAAUqAACnCwAgKwAArAsAIKgFAACoCwAgqQUAAKsLACCuBQAAqQMAIAsqAACWCQAwKwAAmwkAMKgFAACXCQAwqQUAAJgJADCqBQAAmQkAIKsFAACaCQAwrAUAAJoJADCtBQAAmgkAMK4FAACaCQAwrwUAAJwJADCwBQAAnQkAMAsqAACKCQAwKwAAjwkAMKgFAACLCQAwqQUAAIwJADCqBQAAjQkAIKsFAACOCQAwrAUAAI4JADCtBQAAjgkAMK4FAACOCQAwrwUAAJAJADCwBQAAkQkAMAaABAIAAAABpARAAAAAAcQEQAAAAAHFBAgAAAABxgQIAAAAAccEgAAAAAECAAAAtQMAICoAAJUJACADAAAAtQMAICoAAJUJACArAACUCQAgASMAAKoLADAMiQIAAMAGACD9AwAAvQYAMP4DAACzAwAQ_wMAAL0GADCABAIAAAABpARAALoGACHDBAIAuAYAIcQEQAC6BgAhxQQIAL4GACHGBAgAvgYAIccEAAC_BgAg4QQAALwGACACAAAAtQMAICMAAJQJACACAAAAkgkAICMAAJMJACAK_QMAAJEJADD-AwAAkgkAEP8DAACRCQAwgAQCALgGACGkBEAAugYAIcMEAgC4BgAhxARAALoGACHFBAgAvgYAIcYECAC-BgAhxwQAAL8GACAK_QMAAJEJADD-AwAAkgkAEP8DAACRCQAwgAQCALgGACGkBEAAugYAIcMEAgC4BgAhxARAALoGACHFBAgAvgYAIcYECAC-BgAhxwQAAL8GACAGgAQCAKAHACGkBEAAswcAIcQEQACzBwAhxQQIAPgIACHGBAgA-AgAIccEgAAAAAEGgAQCAKAHACGkBEAAswcAIcQEQACzBwAhxQQIAPgIACHGBAgA-AgAIccEgAAAAAEGgAQCAAAAAaQEQAAAAAHEBEAAAAABxQQIAAAAAcYECAAAAAHHBIAAAAABDoAEAgAAAAGkBEAAAAABvQRAAAAAAcUECAAAAAHGBAgAAAABxwSAAAAAAcgECAAAAAHJBAgAAAABygQIAAAAAcsECAAAAAHMBAgAAAABzQQIAAAAAc4EAQAAAAHPBEAAAAABAgAAALEDACAqAAChCQAgAwAAALEDACAqAAChCQAgKwAAoAkAIAEjAACpCwAwFIkCAADABgAg_QMAAMIGADD-AwAArwMAEP8DAADCBgAwgAQCAAAAAaQEQAC6BgAhvQRAALoGACHDBAIAuAYAIcUECAC-BgAhxgQIAL4GACHHBAAAvwYAIMgECAC-BgAhyQQIAL4GACHKBAgAvgYAIcsECAC-BgAhzAQIAL4GACHNBAgAvgYAIc4EAQC5BgAhzwRAAMMGACHiBAAAwQYAIAIAAACxAwAgIwAAoAkAIAIAAACeCQAgIwAAnwkAIBL9AwAAnQkAMP4DAACeCQAQ_wMAAJ0JADCABAIAuAYAIaQEQAC6BgAhvQRAALoGACHDBAIAuAYAIcUECAC-BgAhxgQIAL4GACHHBAAAvwYAIMgECAC-BgAhyQQIAL4GACHKBAgAvgYAIcsECAC-BgAhzAQIAL4GACHNBAgAvgYAIc4EAQC5BgAhzwRAAMMGACES_QMAAJ0JADD-AwAAngkAEP8DAACdCQAwgAQCALgGACGkBEAAugYAIb0EQAC6BgAhwwQCALgGACHFBAgAvgYAIcYECAC-BgAhxwQAAL8GACDIBAgAvgYAIckECAC-BgAhygQIAL4GACHLBAgAvgYAIcwECAC-BgAhzQQIAL4GACHOBAEAuQYAIc8EQADDBgAhDoAEAgCgBwAhpARAALMHACG9BEAAswcAIcUECAD4CAAhxgQIAPgIACHHBIAAAAAByAQIAPgIACHJBAgA-AgAIcoECAD4CAAhywQIAPgIACHMBAgA-AgAIc0ECAD4CAAhzgQBAJ4HACHPBEAAsQcAIQ6ABAIAoAcAIaQEQACzBwAhvQRAALMHACHFBAgA-AgAIcYECAD4CAAhxwSAAAAAAcgECAD4CAAhyQQIAPgIACHKBAgA-AgAIcsECAD4CAAhzAQIAPgIACHNBAgA-AgAIc4EAQCeBwAhzwRAALEHACEOgAQCAAAAAaQEQAAAAAG9BEAAAAABxQQIAAAAAcYECAAAAAHHBIAAAAAByAQIAAAAAckECAAAAAHKBAgAAAABywQIAAAAAcwECAAAAAHNBAgAAAABzgQBAAAAAc8EQAAAAAEDKgAApwsAIKgFAACoCwAgrgUAAKkDACAEKgAAlgkAMKgFAACXCQAwqgUAAJkJACCuBQAAmgkAMAQqAACKCQAwqAUAAIsJADCqBQAAjQkAIK4FAACOCQAwAAAAAAALKgAAqwkAMCsAALAJADCoBQAArAkAMKkFAACtCQAwqgUAAK4JACCrBQAArwkAMKwFAACvCQAwrQUAAK8JADCuBQAArwkAMK8FAACxCQAwsAUAALIJADASigIAAKMJACCLAgAApAkAIIAEAgAAAAGEBAEAAAABpARAAAAAAdAEAQAAAAHSBAEAAAAB0wQBAAAAAdQEAgAAAAHVBAIAAAAB1gQCAAAAAdcEAgAAAAHYBAIAAAAB2QQCAAAAAdoEAgAAAAHbBEAAAAAB3AQgAAAAAd0EQAAAAAECAAAArQMAICoAALYJACADAAAArQMAICoAALYJACArAAC1CQAgASMAAKYLADAXiAIAAMgGACCKAgAAyQYAIIsCAADKBgAg_QMAAMQGADD-AwAAqwMAEP8DAADEBgAwgAQCAAAAAYQEAQC5BgAhpARAALoGACHQBAEAAAAB0QQBALkGACHSBAEAuQYAIdMEAQDFBgAh1AQCALgGACHVBAIAuAYAIdYEAgC4BgAh1wQCALgGACHYBAIAuAYAIdkEAgC4BgAh2gQCAMYGACHbBEAAwwYAIdwEIADHBgAh3QRAAMMGACECAAAArQMAICMAALUJACACAAAAswkAICMAALQJACAU_QMAALIJADD-AwAAswkAEP8DAACyCQAwgAQCALgGACGEBAEAuQYAIaQEQAC6BgAh0AQBALkGACHRBAEAuQYAIdIEAQC5BgAh0wQBAMUGACHUBAIAuAYAIdUEAgC4BgAh1gQCALgGACHXBAIAuAYAIdgEAgC4BgAh2QQCALgGACHaBAIAxgYAIdsEQADDBgAh3AQgAMcGACHdBEAAwwYAIRT9AwAAsgkAMP4DAACzCQAQ_wMAALIJADCABAIAuAYAIYQEAQC5BgAhpARAALoGACHQBAEAuQYAIdEEAQC5BgAh0gQBALkGACHTBAEAxQYAIdQEAgC4BgAh1QQCALgGACHWBAIAuAYAIdcEAgC4BgAh2AQCALgGACHZBAIAuAYAIdoEAgDGBgAh2wRAAMMGACHcBCAAxwYAId0EQADDBgAhEIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIdAEAQCeBwAh0gQBAJ4HACHTBAEAnwcAIdQEAgCgBwAh1QQCAKAHACHWBAIAoAcAIdcEAgCgBwAh2AQCAKAHACHZBAIAoAcAIdoEAgC0BwAh2wRAALEHACHcBCAAsgcAId0EQACxBwAhEooCAACICQAgiwIAAIkJACCABAIAoAcAIYQEAQCeBwAhpARAALMHACHQBAEAngcAIdIEAQCeBwAh0wQBAJ8HACHUBAIAoAcAIdUEAgCgBwAh1gQCAKAHACHXBAIAoAcAIdgEAgCgBwAh2QQCAKAHACHaBAIAtAcAIdsEQACxBwAh3AQgALIHACHdBEAAsQcAIRKKAgAAowkAIIsCAACkCQAggAQCAAAAAYQEAQAAAAGkBEAAAAAB0AQBAAAAAdIEAQAAAAHTBAEAAAAB1AQCAAAAAdUEAgAAAAHWBAIAAAAB1wQCAAAAAdgEAgAAAAHZBAIAAAAB2gQCAAAAAdsEQAAAAAHcBCAAAAAB3QRAAAAAAQQqAACrCQAwqAUAAKwJADCqBQAArgkAIK4FAACvCQAwAAeIAgAAugkAIIoCAAC7CQAgiwIAALwJACDTBAAAmAcAINoEAACYBwAg2wQAAJgHACDdBAAAmAcAIAGMAgAAuAkAIAAAAAAAAAAAAAAAAAWrBQgAAAABsQUIAAAAAbIFCAAAAAGzBQgAAAABtAUIAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAABasFBAAAAAGxBQQAAAABsgUEAAAAAbMFBAAAAAG0BQQAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAFKgAAoQsAICsAAKQLACCoBQAAogsAIKkFAACjCwAgrgUAAJQBACADKgAAoQsAIKgFAACiCwAgrgUAAJQBACAAAAAAAAsqAAD5CQAwKwAA_gkAMKgFAAD6CQAwqQUAAPsJADCqBQAA_AkAIKsFAAD9CQAwrAUAAP0JADCtBQAA_QkAMK4FAAD9CQAwrwUAAP8JADCwBQAAgAoAMAWABAIAAAABpARAAAAAAaUEQAAAAAHABAEAAAABmAUIAAAAAQIAAACYAQAgKgAAhAoAIAMAAACYAQAgKgAAhAoAICsAAIMKACABIwAAoAsAMAtWAADtBgAg_QMAAOwGADD-AwAAlgEAEP8DAADsBgAwgAQCAAAAAaQEQAC6BgAhpQRAALoGACHABAEAuQYAIZcFAgC4BgAhmAUIANEGACGdBQAA6wYAIAIAAACYAQAgIwAAgwoAIAIAAACBCgAgIwAAggoAIAn9AwAAgAoAMP4DAACBCgAQ_wMAAIAKADCABAIAuAYAIaQEQAC6BgAhpQRAALoGACHABAEAuQYAIZcFAgC4BgAhmAUIANEGACEJ_QMAAIAKADD-AwAAgQoAEP8DAACACgAwgAQCALgGACGkBEAAugYAIaUEQAC6BgAhwAQBALkGACGXBQIAuAYAIZgFCADRBgAhBYAEAgCgBwAhpARAALMHACGlBEAAswcAIcAEAQCeBwAhmAUIAMcJACEFgAQCAKAHACGkBEAAswcAIaUEQACzBwAhwAQBAJ4HACGYBQgAxwkAIQWABAIAAAABpARAAAAAAaUEQAAAAAHABAEAAAABmAUIAAAAAQQqAAD5CQAwqAUAAPoJADCqBQAA_AkAIK4FAAD9CQAwAAFXAACGCgAgAAAAAAAAAAAAAAAAAAAACyoAAPEKADArAAD2CgAwqAUAAPIKADCpBQAA8woAMKoFAAD0CgAgqwUAAPUKADCsBQAA9QoAMK0FAAD1CgAwrgUAAPUKADCvBQAA9woAMLAFAAD4CgAwCyoAAOgKADArAADsCgAwqAUAAOkKADCpBQAA6goAMKoFAADrCgAgqwUAAOkIADCsBQAA6QgAMK0FAADpCAAwrgUAAOkIADCvBQAA7QoAMLAFAADsCAAwCyoAANwKADArAADhCgAwqAUAAN0KADCpBQAA3goAMKoFAADfCgAgqwUAAOAKADCsBQAA4AoAMK0FAADgCgAwrgUAAOAKADCvBQAA4goAMLAFAADjCgAwCyoAANAKADArAADVCgAwqAUAANEKADCpBQAA0goAMKoFAADTCgAgqwUAANQKADCsBQAA1AoAMK0FAADUCgAwrgUAANQKADCvBQAA1goAMLAFAADXCgAwCyoAAMQKADArAADJCgAwqAUAAMUKADCpBQAAxgoAMKoFAADHCgAgqwUAAMgKADCsBQAAyAoAMK0FAADICgAwrgUAAMgKADCvBQAAygoAMLAFAADLCgAwCyoAALsKADArAAC_CgAwqAUAALwKADCpBQAAvQoAMKoFAAC-CgAgqwUAAKYIADCsBQAApggAMK0FAACmCAAwrgUAAKYIADCvBQAAwAoAMLAFAACpCAAwCyoAALIKADArAAC2CgAwqAUAALMKADCpBQAAtAoAMKoFAAC1CgAgqwUAANkHADCsBQAA2QcAMK0FAADZBwAwrgUAANkHADCvBQAAtwoAMLAFAADcBwAwCyoAAKkKADArAACtCgAwqAUAAKoKADCpBQAAqwoAMKoFAACsCgAgqwUAANkHADCsBQAA2QcAMK0FAADZBwAwrgUAANkHADCvBQAArgoAMLAFAADcBwAwCyoAAKAKADArAACkCgAwqAUAAKEKADCpBQAAogoAMKoFAACjCgAgqwUAAM0HADCsBQAAzQcAMK0FAADNBwAwrgUAAM0HADCvBQAApQoAMLAFAADQBwAwBRUAAKoHACCABAIAAAABgQQBAAAAAYIEAQAAAAGDBAIAAAABAgAAADEAICoAAKgKACADAAAAMQAgKgAAqAoAICsAAKcKACABIwAAnwsAMAIAAAAxACAjAACnCgAgAgAAANEHACAjAACmCgAgBIAEAgCgBwAhgQQBAJ4HACGCBAEAnwcAIYMEAgCgBwAhBRUAAKgHACCABAIAoAcAIYEEAQCeBwAhggQBAJ8HACGDBAIAoAcAIQUVAACqBwAggAQCAAAAAYEEAQAAAAGCBAEAAAABgwQCAAAAAR4OAADhBwAgDwAA5gcAIBAAAOIHACARAADoBwAgEgAA4wcAIBMAAOQHACAWAADnBwAggAQCAAAAAYEEAQAAAAGRBAEAAAABkgQBAAAAAZMEAQAAAAGUBAIAAAABlQQCAAAAAZYEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGdBAIAAAABnwSAAAAAAaAEAgAAAAGhBCAAAAABogRAAAAAAaMEAQAAAAGkBEAAAAABpQRAAAAAAaYEAgAAAAECAAAAKgAgKgAAsQoAIAMAAAAqACAqAACxCgAgKwAAsAoAIAEjAACeCwAwAgAAACoAICMAALAKACACAAAA3QcAICMAAK8KACAXgAQCAKAHACGBBAEAngcAIZEEAQCfBwAhkgQBAJ8HACGTBAEAnwcAIZQEAgCgBwAhlQQCAKAHACGWBAIAtAcAIZcEAQCeBwAhmAQBAJ4HACGZBAEAnwcAIZoEQACxBwAhmwQBAJ4HACGcBEAAsQcAIZ0EAgC0BwAhnwSAAAAAAaAEAgCgBwAhoQQgALIHACGiBEAAsQcAIaMEAQCfBwAhpARAALMHACGlBEAAswcAIaYEAgC0BwAhHg4AALUHACAPAAC7BwAgEAAAtgcAIBEAALcHACASAAC4BwAgEwAAuQcAIBYAALwHACCABAIAoAcAIYEEAQCeBwAhkQQBAJ8HACGSBAEAnwcAIZMEAQCfBwAhlAQCAKAHACGVBAIAoAcAIZYEAgC0BwAhlwQBAJ4HACGYBAEAngcAIZkEAQCfBwAhmgRAALEHACGbBAEAngcAIZwEQACxBwAhnQQCALQHACGfBIAAAAABoAQCAKAHACGhBCAAsgcAIaIEQACxBwAhowQBAJ8HACGkBEAAswcAIaUEQACzBwAhpgQCALQHACEeDgAA4QcAIA8AAOYHACAQAADiBwAgEQAA6AcAIBIAAOMHACATAADkBwAgFgAA5wcAIIAEAgAAAAGBBAEAAAABkQQBAAAAAZIEAQAAAAGTBAEAAAABlAQCAAAAAZUEAgAAAAGWBAIAAAABlwQBAAAAAZgEAQAAAAGZBAEAAAABmgRAAAAAAZsEAQAAAAGcBEAAAAABnQQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABHg4AAOEHACAPAADmBwAgEAAA4gcAIBEAAOgHACASAADjBwAgFAAA5QcAIBYAAOcHACCABAIAAAABgQQBAAAAAZEEAQAAAAGSBAEAAAABkwQBAAAAAZQEAgAAAAGVBAIAAAABlgQCAAAAAZcEAQAAAAGYBAEAAAABmQQBAAAAAZoEQAAAAAGbBAEAAAABnARAAAAAAZ4EAgAAAAGfBIAAAAABoAQCAAAAAaEEIAAAAAGiBEAAAAABowQBAAAAAaQEQAAAAAGlBEAAAAABpgQCAAAAAQIAAAAqACAqAAC6CgAgAwAAACoAICoAALoKACArAAC5CgAgASMAAJ0LADACAAAAKgAgIwAAuQoAIAIAAADdBwAgIwAAuAoAIBeABAIAoAcAIYEEAQCeBwAhkQQBAJ8HACGSBAEAnwcAIZMEAQCfBwAhlAQCAKAHACGVBAIAoAcAIZYEAgC0BwAhlwQBAJ4HACGYBAEAngcAIZkEAQCfBwAhmgRAALEHACGbBAEAngcAIZwEQACxBwAhngQCAKAHACGfBIAAAAABoAQCAKAHACGhBCAAsgcAIaIEQACxBwAhowQBAJ8HACGkBEAAswcAIaUEQACzBwAhpgQCALQHACEeDgAAtQcAIA8AALsHACAQAAC2BwAgEQAAtwcAIBIAALgHACAUAAC6BwAgFgAAvAcAIIAEAgCgBwAhgQQBAJ4HACGRBAEAnwcAIZIEAQCfBwAhkwQBAJ8HACGUBAIAoAcAIZUEAgCgBwAhlgQCALQHACGXBAEAngcAIZgEAQCeBwAhmQQBAJ8HACGaBEAAsQcAIZsEAQCeBwAhnARAALEHACGeBAIAoAcAIZ8EgAAAAAGgBAIAoAcAIaEEIACyBwAhogRAALEHACGjBAEAnwcAIaQEQACzBwAhpQRAALMHACGmBAIAtAcAIR4OAADhBwAgDwAA5gcAIBAAAOIHACARAADoBwAgEgAA4wcAIBQAAOUHACAWAADnBwAggAQCAAAAAYEEAQAAAAGRBAEAAAABkgQBAAAAAZMEAQAAAAGUBAIAAAABlQQCAAAAAZYEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGeBAIAAAABnwSAAAAAAaAEAgAAAAGhBCAAAAABogRAAAAAAaMEAQAAAAGkBEAAAAABpQRAAAAAAaYEAgAAAAEHDgAAgggAIIAEAgAAAAGBBAEAAAABkQQBAAAAAZQEAgAAAAGkBEAAAAABrwQBAAAAAQIAAAAiACAqAADDCgAgAwAAACIAICoAAMMKACArAADCCgAgASMAAJwLADACAAAAIgAgIwAAwgoAIAIAAACqCAAgIwAAwQoAIAaABAIAoAcAIYEEAQCeBwAhkQQBAJ8HACGUBAIAoAcAIaQEQACzBwAhrwQBAJ4HACEHDgAAgAgAIIAEAgCgBwAhgQQBAJ4HACGRBAEAnwcAIZQEAgCgBwAhpARAALMHACGvBAEAngcAIQcOAACCCAAggAQCAAAAAYEEAQAAAAGRBAEAAAABlAQCAAAAAaQEQAAAAAGvBAEAAAABDw8AAK8IACAXAACxCAAgGAAAsAgAIIAEAgAAAAGBBAEAAAABhAQBAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbAEAQAAAAGyBCAAAAABswSAAAAAAbQEgAAAAAG1BAEAAAABtgQBAAAAAQIAAAAeACAqAADPCgAgAwAAAB4AICoAAM8KACArAADOCgAgASMAAJsLADAUDQAA-AYAIA8AAIcHACAXAAD-BgAgGAAAiAcAIP0DAACGBwAw_gMAABwAEP8DAACGBwAwgAQCAAAAAYEEAQAAAAGEBAEAuQYAIaEEIADHBgAhpARAALoGACGlBEAAugYAIbAEAQAAAAGxBAIAuAYAIbIEIADHBgAhswQAAPAGACC0BAAAvwYAILUEAQDFBgAhtgQBALkGACECAAAAHgAgIwAAzgoAIAIAAADMCgAgIwAAzQoAIBD9AwAAywoAMP4DAADMCgAQ_wMAAMsKADCABAIAuAYAIYEEAQC5BgAhhAQBALkGACGhBCAAxwYAIaQEQAC6BgAhpQRAALoGACGwBAEAuQYAIbEEAgC4BgAhsgQgAMcGACGzBAAA8AYAILQEAAC_BgAgtQQBAMUGACG2BAEAuQYAIRD9AwAAywoAMP4DAADMCgAQ_wMAAMsKADCABAIAuAYAIYEEAQC5BgAhhAQBALkGACGhBCAAxwYAIaQEQAC6BgAhpQRAALoGACGwBAEAuQYAIbEEAgC4BgAhsgQgAMcGACGzBAAA8AYAILQEAAC_BgAgtQQBAMUGACG2BAEAuQYAIQyABAIAoAcAIYEEAQCeBwAhhAQBAJ4HACGhBCAAsgcAIaQEQACzBwAhpQRAALMHACGwBAEAngcAIbIEIACyBwAhswSAAAAAAbQEgAAAAAG1BAEAnwcAIbYEAQCeBwAhDw8AAIoIACAXAACMCAAgGAAAiwgAIIAEAgCgBwAhgQQBAJ4HACGEBAEAngcAIaEEIACyBwAhpARAALMHACGlBEAAswcAIbAEAQCeBwAhsgQgALIHACGzBIAAAAABtASAAAAAAbUEAQCfBwAhtgQBAJ4HACEPDwAArwgAIBcAALEIACAYAACwCAAggAQCAAAAAYEEAQAAAAGEBAEAAAABoQQgAAAAAaQEQAAAAAGlBEAAAAABsAQBAAAAAbIEIAAAAAGzBIAAAAABtASAAAAAAbUEAQAAAAG2BAEAAAABBwqAAAAAAYAEAgAAAAGEBAEAAAABpARAAAAAAaUEQAAAAAG3BAEAAAABuAQgAAAAAQIAAAAYACAqAADbCgAgAwAAABgAICoAANsKACArAADaCgAgASMAAJoLADAMAwAA_wYAIAoAAPAGACD9AwAAjwcAMP4DAAAWABD_AwAAjwcAMIAEAgAAAAGEBAEAuQYAIZAEAgDGBgAhpARAALoGACGlBEAAugYAIbcEAQC5BgAhuAQgAMcGACECAAAAGAAgIwAA2goAIAIAAADYCgAgIwAA2QoAIAsKAADwBgAg_QMAANcKADD-AwAA2AoAEP8DAADXCgAwgAQCALgGACGEBAEAuQYAIZAEAgDGBgAhpARAALoGACGlBEAAugYAIbcEAQC5BgAhuAQgAMcGACELCgAA8AYAIP0DAADXCgAw_gMAANgKABD_AwAA1woAMIAEAgC4BgAhhAQBALkGACGQBAIAxgYAIaQEQAC6BgAhpQRAALoGACG3BAEAuQYAIbgEIADHBgAhBwqAAAAAAYAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhtwQBAJ4HACG4BCAAsgcAIQcKgAAAAAGABAIAoAcAIYQEAQCeBwAhpARAALMHACGlBEAAswcAIbcEAQCeBwAhuAQgALIHACEHCoAAAAABgAQCAAAAAYQEAQAAAAGkBEAAAAABpQRAAAAAAbcEAQAAAAG4BCAAAAABCAoAANQIACCABAIAAAABlwQBAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbcEAQAAAAG6BAEAAAABAgAAAA8AICoAAOcKACADAAAADwAgKgAA5woAICsAAOYKACABIwAAmQsAMA0DAAD4BgAgCgAAkwcAIP0DAACSBwAw_gMAAA0AEP8DAACSBwAwgAQCAAAAAZAEAgC4BgAhlwQBALkGACGhBCAAxwYAIaQEQAC6BgAhpQRAALoGACG3BAEAuQYAIboEAQC5BgAhAgAAAA8AICMAAOYKACACAAAA5AoAICMAAOUKACAL_QMAAOMKADD-AwAA5AoAEP8DAADjCgAwgAQCALgGACGQBAIAuAYAIZcEAQC5BgAhoQQgAMcGACGkBEAAugYAIaUEQAC6BgAhtwQBALkGACG6BAEAuQYAIQv9AwAA4woAMP4DAADkCgAQ_wMAAOMKADCABAIAuAYAIZAEAgC4BgAhlwQBALkGACGhBCAAxwYAIaQEQAC6BgAhpQRAALoGACG3BAEAuQYAIboEAQC5BgAhB4AEAgCgBwAhlwQBAJ4HACGhBCAAsgcAIaQEQACzBwAhpQRAALMHACG3BAEAngcAIboEAQCeBwAhCAoAAMYIACCABAIAoAcAIZcEAQCeBwAhoQQgALIHACGkBEAAswcAIaUEQACzBwAhtwQBAJ4HACG6BAEAngcAIQgKAADUCAAggAQCAAAAAZcEAQAAAAGhBCAAAAABpARAAAAAAaUEQAAAAAG3BAEAAAABugQBAAAAAQgEAADcCAAggAQCAAAAAaQEQAAAAAG6BAEAAAABvAQCAAAAAb0EAQAAAAG-BAIAAAABvwQBAAAAAQIAAAAJACAqAADwCgAgAwAAAAkAICoAAPAKACArAADvCgAgASMAAJgLADACAAAACQAgIwAA7woAIAIAAADtCAAgIwAA7goAIAeABAIAoAcAIaQEQACzBwAhugQBAJ4HACG8BAIAoAcAIb0EAQCeBwAhvgQCAKAHACG_BAEAngcAIQgEAADaCAAggAQCAKAHACGkBEAAswcAIboEAQCeBwAhvAQCAKAHACG9BAEAngcAIb4EAgCgBwAhvwQBAJ4HACEIBAAA3AgAIIAEAgAAAAGkBEAAAAABugQBAAAAAbwEAgAAAAG9BAEAAAABvgQCAAAAAb8EAQAAAAEMBQAA8ggAIIAEAgAAAAGEBAEAAAABoAQCAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbUEAQAAAAG6BAEAAAABwAQBAAAAAcEEAgAAAAHCBAEAAAABAgAAAAUAICoAAPwKACADAAAABQAgKgAA_AoAICsAAPsKACABIwAAlwsAMBEDAAD4BgAgBQAAiwcAIP0DAACXBwAw_gMAAAMAEP8DAACXBwAwgAQCAAAAAYQEAQC5BgAhkAQCALgGACGgBAIAuAYAIaEEIADHBgAhpARAALoGACGlBEAAugYAIbUEAQC5BgAhugQBALkGACHABAEAuQYAIcEEAgC4BgAhwgQBALkGACECAAAABQAgIwAA-woAIAIAAAD5CgAgIwAA-goAIA_9AwAA-AoAMP4DAAD5CgAQ_wMAAPgKADCABAIAuAYAIYQEAQC5BgAhkAQCALgGACGgBAIAuAYAIaEEIADHBgAhpARAALoGACGlBEAAugYAIbUEAQC5BgAhugQBALkGACHABAEAuQYAIcEEAgC4BgAhwgQBALkGACEP_QMAAPgKADD-AwAA-QoAEP8DAAD4CgAwgAQCALgGACGEBAEAuQYAIZAEAgC4BgAhoAQCALgGACGhBCAAxwYAIaQEQAC6BgAhpQRAALoGACG1BAEAuQYAIboEAQC5BgAhwAQBALkGACHBBAIAuAYAIcIEAQC5BgAhC4AEAgCgBwAhhAQBAJ4HACGgBAIAoAcAIaEEIACyBwAhpARAALMHACGlBEAAswcAIbUEAQCeBwAhugQBAJ4HACHABAEAngcAIcEEAgCgBwAhwgQBAJ4HACEMBQAA5AgAIIAEAgCgBwAhhAQBAJ4HACGgBAIAoAcAIaEEIACyBwAhpARAALMHACGlBEAAswcAIbUEAQCeBwAhugQBAJ4HACHABAEAngcAIcEEAgCgBwAhwgQBAJ4HACEMBQAA8ggAIIAEAgAAAAGEBAEAAAABoAQCAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbUEAQAAAAG6BAEAAAABwAQBAAAAAcEEAgAAAAHCBAEAAAABBCoAAPEKADCoBQAA8goAMKoFAAD0CgAgrgUAAPUKADAEKgAA6AoAMKgFAADpCgAwqgUAAOsKACCuBQAA6QgAMAQqAADcCgAwqAUAAN0KADCqBQAA3woAIK4FAADgCgAwBCoAANAKADCoBQAA0QoAMKoFAADTCgAgrgUAANQKADAEKgAAxAoAMKgFAADFCgAwqgUAAMcKACCuBQAAyAoAMAQqAAC7CgAwqAUAALwKADCqBQAAvgoAIK4FAACmCAAwBCoAALIKADCoBQAAswoAMKoFAAC1CgAgrgUAANkHADAEKgAAqQoAMKgFAACqCgAwqgUAAKwKACCuBQAA2QcAMAQqAACgCgAwqAUAAKEKADCqBQAAowoAIK4FAADNBwAwAAAAAAAAAAATDgAAkAsAIA8AAI0LACAQAACRCwAgEQAAjgsAIBIAAIwLACATAACPCwAgFAAAjwsAIBYAAJILACCRBAAAmAcAIJIEAACYBwAgkwQAAJgHACCWBAAAmAcAIJkEAACYBwAgmgQAAJgHACCcBAAAmAcAIJ0EAACYBwAgogQAAJgHACCjBAAAmAcAIKYEAACYBwAgCQcAAIYLACAIAACHCwAgCwAAiAsAIAwAAIkLACAZAACKCwAgGgAAiwsAIBsAAIwLACAcAACMCwAgHQAAjQsAIAYNAACPCwAgDwAAiwsAIBcAAIwLACAYAACTCwAgtAQAAJgHACC1BAAAmAcAIAQOAACQCwAgFwAAjAsAIJEEAACYBwAgrQQAAJgHACAAAAIDAACPCwAgCgAAlQsAIAACAwAAjwsAIAUAAIcLACALgAQCAAAAAYQEAQAAAAGgBAIAAAABoQQgAAAAAaQEQAAAAAGlBEAAAAABtQQBAAAAAboEAQAAAAHABAEAAAABwQQCAAAAAcIEAQAAAAEHgAQCAAAAAaQEQAAAAAG6BAEAAAABvAQCAAAAAb0EAQAAAAG-BAIAAAABvwQBAAAAAQeABAIAAAABlwQBAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbcEAQAAAAG6BAEAAAABBwqAAAAAAYAEAgAAAAGEBAEAAAABpARAAAAAAaUEQAAAAAG3BAEAAAABuAQgAAAAAQyABAIAAAABgQQBAAAAAYQEAQAAAAGhBCAAAAABpARAAAAAAaUEQAAAAAGwBAEAAAABsgQgAAAAAbMEgAAAAAG0BIAAAAABtQQBAAAAAbYEAQAAAAEGgAQCAAAAAYEEAQAAAAGRBAEAAAABlAQCAAAAAaQEQAAAAAGvBAEAAAABF4AEAgAAAAGBBAEAAAABkQQBAAAAAZIEAQAAAAGTBAEAAAABlAQCAAAAAZUEAgAAAAGWBAIAAAABlwQBAAAAAZgEAQAAAAGZBAEAAAABmgRAAAAAAZsEAQAAAAGcBEAAAAABngQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABF4AEAgAAAAGBBAEAAAABkQQBAAAAAZIEAQAAAAGTBAEAAAABlAQCAAAAAZUEAgAAAAGWBAIAAAABlwQBAAAAAZgEAQAAAAGZBAEAAAABmgRAAAAAAZsEAQAAAAGcBEAAAAABnQQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABBIAEAgAAAAGBBAEAAAABggQBAAAAAYMEAgAAAAEFgAQCAAAAAaQEQAAAAAGlBEAAAAABwAQBAAAAAZgFCAAAAAEKgAQCAAAAAYQEAQAAAAGXBAEAAAABpARAAAAAAaUEQAAAAAG_BAEAAAABmQUBAAAAAZoFAQAAAAGbBQEAAAABnAUgAAAAAQIAAACUAQAgKgAAoQsAIAMAAACcAQAgKgAAoQsAICsAAKULACAMAAAAnAEAICMAAKULACCABAIAoAcAIYQEAQCeBwAhlwQBAJ4HACGkBEAAswcAIaUEQACzBwAhvwQBAJ4HACGZBQEAngcAIZoFAQCeBwAhmwUBAJ4HACGcBSAAsgcAIQqABAIAoAcAIYQEAQCeBwAhlwQBAJ4HACGkBEAAswcAIaUEQACzBwAhvwQBAJ4HACGZBQEAngcAIZoFAQCeBwAhmwUBAJ4HACGcBSAAsgcAIRCABAIAAAABhAQBAAAAAaQEQAAAAAHQBAEAAAAB0gQBAAAAAdMEAQAAAAHUBAIAAAAB1QQCAAAAAdYEAgAAAAHXBAIAAAAB2AQCAAAAAdkEAgAAAAHaBAIAAAAB2wRAAAAAAdwEIAAAAAHdBEAAAAABBIAEAgAAAAGEBAEAAAABpARAAAAAAdEEAQAAAAECAAAAqQMAICoAAKcLACAOgAQCAAAAAaQEQAAAAAG9BEAAAAABxQQIAAAAAcYECAAAAAHHBIAAAAAByAQIAAAAAckECAAAAAHKBAgAAAABywQIAAAAAcwECAAAAAHNBAgAAAABzgQBAAAAAc8EQAAAAAEGgAQCAAAAAaQEQAAAAAHEBEAAAAABxQQIAAAAAcYECAAAAAHHBIAAAAABAwAAALsDACAqAACnCwAgKwAArQsAIAYAAAC7AwAgIwAArQsAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIdEEAQCeBwAhBIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIdEEAQCeBwAhE4gCAACiCQAgiwIAAKQJACCABAIAAAABhAQBAAAAAaQEQAAAAAHQBAEAAAAB0QQBAAAAAdIEAQAAAAHTBAEAAAAB1AQCAAAAAdUEAgAAAAHWBAIAAAAB1wQCAAAAAdgEAgAAAAHZBAIAAAAB2gQCAAAAAdsEQAAAAAHcBCAAAAAB3QRAAAAAAQIAAACtAwAgKgAArgsAIAMAAACrAwAgKgAArgsAICsAALILACAVAAAAqwMAICMAALILACCIAgAAhwkAIIsCAACJCQAggAQCAKAHACGEBAEAngcAIaQEQACzBwAh0AQBAJ4HACHRBAEAngcAIdIEAQCeBwAh0wQBAJ8HACHUBAIAoAcAIdUEAgCgBwAh1gQCAKAHACHXBAIAoAcAIdgEAgCgBwAh2QQCAKAHACHaBAIAtAcAIdsEQACxBwAh3AQgALIHACHdBEAAsQcAIROIAgAAhwkAIIsCAACJCQAggAQCAKAHACGEBAEAngcAIaQEQACzBwAh0AQBAJ4HACHRBAEAngcAIdIEAQCeBwAh0wQBAJ8HACHUBAIAoAcAIdUEAgCgBwAh1gQCAKAHACHXBAIAoAcAIdgEAgCgBwAh2QQCAKAHACHaBAIAtAcAIdsEQACxBwAh3AQgALIHACHdBEAAsQcAIROIAgAAogkAIIoCAACjCQAggAQCAAAAAYQEAQAAAAGkBEAAAAAB0AQBAAAAAdEEAQAAAAHSBAEAAAAB0wQBAAAAAdQEAgAAAAHVBAIAAAAB1gQCAAAAAdcEAgAAAAHYBAIAAAAB2QQCAAAAAdoEAgAAAAHbBEAAAAAB3AQgAAAAAd0EQAAAAAECAAAArQMAICoAALMLACADAAAAqwMAICoAALMLACArAAC3CwAgFQAAAKsDACAjAAC3CwAgiAIAAIcJACCKAgAAiAkAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIdAEAQCeBwAh0QQBAJ4HACHSBAEAngcAIdMEAQCfBwAh1AQCAKAHACHVBAIAoAcAIdYEAgCgBwAh1wQCAKAHACHYBAIAoAcAIdkEAgCgBwAh2gQCALQHACHbBEAAsQcAIdwEIACyBwAh3QRAALEHACETiAIAAIcJACCKAgAAiAkAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIdAEAQCeBwAh0QQBAJ4HACHSBAEAngcAIdMEAQCfBwAh1AQCAKAHACHVBAIAoAcAIdYEAgCgBwAh1wQCAKAHACHYBAIAoAcAIdkEAgCgBwAh2gQCALQHACHbBEAAsQcAIdwEIACyBwAh3QRAALEHACEOCAAA_goAIAsAAP8KACAMAACACwAgGQAAgQsAIBoAAIILACAbAACDCwAgHAAAhAsAIB0AAIULACCABAIAAAABhAQBAAAAAaQEQAAAAAGlBEAAAAABnwUBAAAAAaAFAQAAAAECAAAAAQAgKgAAuAsAIAeABAIAAAABkAQCAAAAAaQEQAAAAAG6BAEAAAABvQQBAAAAAb4EAgAAAAG_BAEAAAABAwAAABoAICoAALgLACArAAC9CwAgEAAAABoAIAgAAJgKACALAACZCgAgDAAAmgoAIBkAAJsKACAaAACcCgAgGwAAnQoAIBwAAJ4KACAdAACfCgAgIwAAvQsAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhnwUBAJ4HACGgBQEAngcAIQ4IAACYCgAgCwAAmQoAIAwAAJoKACAZAACbCgAgGgAAnAoAIBsAAJ0KACAcAACeCgAgHQAAnwoAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhnwUBAJ4HACGgBQEAngcAIQ4HAAD9CgAgCwAA_woAIAwAAIALACAZAACBCwAgGgAAggsAIBsAAIMLACAcAACECwAgHQAAhQsAIIAEAgAAAAGEBAEAAAABpARAAAAAAaUEQAAAAAGfBQEAAAABoAUBAAAAAQIAAAABACAqAAC-CwAgDQMAAPEIACCABAIAAAABhAQBAAAAAZAEAgAAAAGgBAIAAAABoQQgAAAAAaQEQAAAAAGlBEAAAAABtQQBAAAAAboEAQAAAAHABAEAAAABwQQCAAAAAcIEAQAAAAECAAAABQAgKgAAwAsAIAMAAAAaACAqAAC-CwAgKwAAxAsAIBAAAAAaACAHAACXCgAgCwAAmQoAIAwAAJoKACAZAACbCgAgGgAAnAoAIBsAAJ0KACAcAACeCgAgHQAAnwoAICMAAMQLACCABAIAoAcAIYQEAQCeBwAhpARAALMHACGlBEAAswcAIZ8FAQCeBwAhoAUBAJ4HACEOBwAAlwoAIAsAAJkKACAMAACaCgAgGQAAmwoAIBoAAJwKACAbAACdCgAgHAAAngoAIB0AAJ8KACCABAIAoAcAIYQEAQCeBwAhpARAALMHACGlBEAAswcAIZ8FAQCeBwAhoAUBAJ4HACEDAAAAAwAgKgAAwAsAICsAAMcLACAPAAAAAwAgAwAA4wgAICMAAMcLACCABAIAoAcAIYQEAQCeBwAhkAQCAKAHACGgBAIAoAcAIaEEIACyBwAhpARAALMHACGlBEAAswcAIbUEAQCeBwAhugQBAJ4HACHABAEAngcAIcEEAgCgBwAhwgQBAJ4HACENAwAA4wgAIIAEAgCgBwAhhAQBAJ4HACGQBAIAoAcAIaAEAgCgBwAhoQQgALIHACGkBEAAswcAIaUEQACzBwAhtQQBAJ4HACG6BAEAngcAIcAEAQCeBwAhwQQCAKAHACHCBAEAngcAIQ4HAAD9CgAgCAAA_goAIAwAAIALACAZAACBCwAgGgAAggsAIBsAAIMLACAcAACECwAgHQAAhQsAIIAEAgAAAAGEBAEAAAABpARAAAAAAaUEQAAAAAGfBQEAAAABoAUBAAAAAQIAAAABACAqAADICwAgCIAEAgAAAAGXBAEAAAABnARAAAAAAaAEAgAAAAGkBEAAAAABpQRAAAAAAboEAQAAAAG7BCAAAAABAwAAABoAICoAAMgLACArAADNCwAgEAAAABoAIAcAAJcKACAIAACYCgAgDAAAmgoAIBkAAJsKACAaAACcCgAgGwAAnQoAIBwAAJ4KACAdAACfCgAgIwAAzQsAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhnwUBAJ4HACGgBQEAngcAIQ4HAACXCgAgCAAAmAoAIAwAAJoKACAZAACbCgAgGgAAnAoAIBsAAJ0KACAcAACeCgAgHQAAnwoAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhnwUBAJ4HACGgBQEAngcAIQkDAADTCAAggAQCAAAAAZAEAgAAAAGXBAEAAAABoQQgAAAAAaQEQAAAAAGlBEAAAAABtwQBAAAAAboEAQAAAAECAAAADwAgKgAAzgsAIAMAAAANACAqAADOCwAgKwAA0gsAIAsAAAANACADAADFCAAgIwAA0gsAIIAEAgCgBwAhkAQCAKAHACGXBAEAngcAIaEEIACyBwAhpARAALMHACGlBEAAswcAIbcEAQCeBwAhugQBAJ4HACEJAwAAxQgAIIAEAgCgBwAhkAQCAKAHACGXBAEAngcAIaEEIACyBwAhpARAALMHACGlBEAAswcAIbcEAQCeBwAhugQBAJ4HACEOBwAA_QoAIAgAAP4KACALAAD_CgAgGQAAgQsAIBoAAIILACAbAACDCwAgHAAAhAsAIB0AAIULACCABAIAAAABhAQBAAAAAaQEQAAAAAGlBEAAAAABnwUBAAAAAaAFAQAAAAECAAAAAQAgKgAA0wsAIAMAAAAaACAqAADTCwAgKwAA1wsAIBAAAAAaACAHAACXCgAgCAAAmAoAIAsAAJkKACAZAACbCgAgGgAAnAoAIBsAAJ0KACAcAACeCgAgHQAAnwoAICMAANcLACCABAIAoAcAIYQEAQCeBwAhpARAALMHACGlBEAAswcAIZ8FAQCeBwAhoAUBAJ4HACEOBwAAlwoAIAgAAJgKACALAACZCgAgGQAAmwoAIBoAAJwKACAbAACdCgAgHAAAngoAIB0AAJ8KACCABAIAoAcAIYQEAQCeBwAhpARAALMHACGlBEAAswcAIZ8FAQCeBwAhoAUBAJ4HACEOBwAA_QoAIAgAAP4KACALAAD_CgAgDAAAgAsAIBoAAIILACAbAACDCwAgHAAAhAsAIB0AAIULACCABAIAAAABhAQBAAAAAaQEQAAAAAGlBEAAAAABnwUBAAAAAaAFAQAAAAECAAAAAQAgKgAA2AsAIAaABAIAAAABgQQBAAAAAZAEAgAAAAGRBAEAAAABpARAAAAAAa8EAQAAAAEJgAQCAAAAAYEEAQAAAAGEBAEAAAABkQQBAAAAAaAEAgAAAAGkBEAAAAABpQRAAAAAAa0EAQAAAAGuBAEAAAABF4AEAgAAAAGBBAEAAAABkQQBAAAAAZIEAQAAAAGTBAEAAAABlQQCAAAAAZYEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGdBAIAAAABngQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABAwAAABoAICoAANgLACArAADfCwAgEAAAABoAIAcAAJcKACAIAACYCgAgCwAAmQoAIAwAAJoKACAaAACcCgAgGwAAnQoAIBwAAJ4KACAdAACfCgAgIwAA3wsAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhnwUBAJ4HACGgBQEAngcAIQ4HAACXCgAgCAAAmAoAIAsAAJkKACAMAACaCgAgGgAAnAoAIBsAAJ0KACAcAACeCgAgHQAAnwoAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhnwUBAJ4HACGgBQEAngcAIQ4HAAD9CgAgCAAA_goAIAsAAP8KACAMAACACwAgGQAAgQsAIBsAAIMLACAcAACECwAgHQAAhQsAIIAEAgAAAAGEBAEAAAABpARAAAAAAaUEQAAAAAGfBQEAAAABoAUBAAAAAQIAAAABACAqAADgCwAgEA0AAK4IACAXAACxCAAgGAAAsAgAIIAEAgAAAAGBBAEAAAABhAQBAAAAAaEEIAAAAAGkBEAAAAABpQRAAAAAAbAEAQAAAAGxBAIAAAABsgQgAAAAAbMEgAAAAAG0BIAAAAABtQQBAAAAAbYEAQAAAAECAAAAHgAgKgAA4gsAIAMAAAAaACAqAADgCwAgKwAA5gsAIBAAAAAaACAHAACXCgAgCAAAmAoAIAsAAJkKACAMAACaCgAgGQAAmwoAIBsAAJ0KACAcAACeCgAgHQAAnwoAICMAAOYLACCABAIAoAcAIYQEAQCeBwAhpARAALMHACGlBEAAswcAIZ8FAQCeBwAhoAUBAJ4HACEOBwAAlwoAIAgAAJgKACALAACZCgAgDAAAmgoAIBkAAJsKACAbAACdCgAgHAAAngoAIB0AAJ8KACCABAIAoAcAIYQEAQCeBwAhpARAALMHACGlBEAAswcAIZ8FAQCeBwAhoAUBAJ4HACEDAAAAHAAgKgAA4gsAICsAAOkLACASAAAAHAAgDQAAiQgAIBcAAIwIACAYAACLCAAgIwAA6QsAIIAEAgCgBwAhgQQBAJ4HACGEBAEAngcAIaEEIACyBwAhpARAALMHACGlBEAAswcAIbAEAQCeBwAhsQQCAKAHACGyBCAAsgcAIbMEgAAAAAG0BIAAAAABtQQBAJ8HACG2BAEAngcAIRANAACJCAAgFwAAjAgAIBgAAIsIACCABAIAoAcAIYEEAQCeBwAhhAQBAJ4HACGhBCAAsgcAIaQEQACzBwAhpQRAALMHACGwBAEAngcAIbEEAgCgBwAhsgQgALIHACGzBIAAAAABtASAAAAAAbUEAQCfBwAhtgQBAJ4HACEQDQAArggAIA8AAK8IACAXAACxCAAggAQCAAAAAYEEAQAAAAGEBAEAAAABoQQgAAAAAaQEQAAAAAGlBEAAAAABsAQBAAAAAbEEAgAAAAGyBCAAAAABswSAAAAAAbQEgAAAAAG1BAEAAAABtgQBAAAAAQIAAAAeACAqAADqCwAgF4AEAgAAAAGBBAEAAAABkQQBAAAAAZIEAQAAAAGTBAEAAAABlAQCAAAAAZYEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGdBAIAAAABngQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABAwAAABwAICoAAOoLACArAADvCwAgEgAAABwAIA0AAIkIACAPAACKCAAgFwAAjAgAICMAAO8LACCABAIAoAcAIYEEAQCeBwAhhAQBAJ4HACGhBCAAsgcAIaQEQACzBwAhpQRAALMHACGwBAEAngcAIbEEAgCgBwAhsgQgALIHACGzBIAAAAABtASAAAAAAbUEAQCfBwAhtgQBAJ4HACEQDQAAiQgAIA8AAIoIACAXAACMCAAggAQCAKAHACGBBAEAngcAIYQEAQCeBwAhoQQgALIHACGkBEAAswcAIaUEQACzBwAhsAQBAJ4HACGxBAIAoAcAIbIEIACyBwAhswSAAAAAAbQEgAAAAAG1BAEAnwcAIbYEAQCeBwAhHw4AAOEHACAPAADmBwAgEAAA4gcAIBEAAOgHACATAADkBwAgFAAA5QcAIBYAAOcHACCABAIAAAABgQQBAAAAAZEEAQAAAAGSBAEAAAABkwQBAAAAAZQEAgAAAAGVBAIAAAABlgQCAAAAAZcEAQAAAAGYBAEAAAABmQQBAAAAAZoEQAAAAAGbBAEAAAABnARAAAAAAZ0EAgAAAAGeBAIAAAABnwSAAAAAAaAEAgAAAAGhBCAAAAABogRAAAAAAaMEAQAAAAGkBEAAAAABpQRAAAAAAaYEAgAAAAECAAAAKgAgKgAA8AsAIA4HAAD9CgAgCAAA_goAIAsAAP8KACAMAACACwAgGQAAgQsAIBoAAIILACAbAACDCwAgHQAAhQsAIIAEAgAAAAGEBAEAAAABpARAAAAAAaUEQAAAAAGfBQEAAAABoAUBAAAAAQIAAAABACAqAADyCwAgDgcAAP0KACAIAAD-CgAgCwAA_woAIAwAAIALACAZAACBCwAgGgAAggsAIBwAAIQLACAdAACFCwAggAQCAAAAAYQEAQAAAAGkBEAAAAABpQRAAAAAAZ8FAQAAAAGgBQEAAAABAgAAAAEAICoAAPQLACALDgAA-QcAIIAEAgAAAAGBBAEAAAABhAQBAAAAAZEEAQAAAAGUBAIAAAABoAQCAAAAAaQEQAAAAAGlBEAAAAABrQQBAAAAAa4EAQAAAAECAAAAJgAgKgAA9gsAIBANAACuCAAgDwAArwgAIBgAALAIACCABAIAAAABgQQBAAAAAYQEAQAAAAGhBCAAAAABpARAAAAAAaUEQAAAAAGwBAEAAAABsQQCAAAAAbIEIAAAAAGzBIAAAAABtASAAAAAAbUEAQAAAAG2BAEAAAABAgAAAB4AICoAAPgLACAXgAQCAAAAAYEEAQAAAAGRBAEAAAABkgQBAAAAAZMEAQAAAAGUBAIAAAABlQQCAAAAAZcEAQAAAAGYBAEAAAABmQQBAAAAAZoEQAAAAAGbBAEAAAABnARAAAAAAZ0EAgAAAAGeBAIAAAABnwSAAAAAAaAEAgAAAAGhBCAAAAABogRAAAAAAaMEAQAAAAGkBEAAAAABpQRAAAAAAaYEAgAAAAEEgAQCAAAAAYEEAQAAAAGCBAEAAAABkAQCAAAAAQSABAIAAAABgQQBAAAAAYIEAQAAAAGEBAEAAAABAwAAABoAICoAAPILACArAAD_CwAgEAAAABoAIAcAAJcKACAIAACYCgAgCwAAmQoAIAwAAJoKACAZAACbCgAgGgAAnAoAIBsAAJ0KACAdAACfCgAgIwAA_wsAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhnwUBAJ4HACGgBQEAngcAIQ4HAACXCgAgCAAAmAoAIAsAAJkKACAMAACaCgAgGQAAmwoAIBoAAJwKACAbAACdCgAgHQAAnwoAIIAEAgCgBwAhhAQBAJ4HACGkBEAAswcAIaUEQACzBwAhnwUBAJ4HACGgBQEAngcAIQMAAAAaACAqAAD0CwAgKwAAggwAIBAAAAAaACAHAACXCgAgCAAAmAoAIAsAAJkKACAMAACaCgAgGQAAmwoAIBoAAJwKACAcAACeCgAgHQAAnwoAICMAAIIMACCABAIAoAcAIYQEAQCeBwAhpARAALMHACGlBEAAswcAIZ8FAQCeBwAhoAUBAJ4HACEOBwAAlwoAIAgAAJgKACALAACZCgAgDAAAmgoAIBkAAJsKACAaAACcCgAgHAAAngoAIB0AAJ8KACCABAIAoAcAIYQEAQCeBwAhpARAALMHACGlBEAAswcAIZ8FAQCeBwAhoAUBAJ4HACEDAAAAKAAgKgAA8AsAICsAAIUMACAhAAAAKAAgDgAAtQcAIA8AALsHACAQAAC2BwAgEQAAtwcAIBMAALkHACAUAAC6BwAgFgAAvAcAICMAAIUMACCABAIAoAcAIYEEAQCeBwAhkQQBAJ8HACGSBAEAnwcAIZMEAQCfBwAhlAQCAKAHACGVBAIAoAcAIZYEAgC0BwAhlwQBAJ4HACGYBAEAngcAIZkEAQCfBwAhmgRAALEHACGbBAEAngcAIZwEQACxBwAhnQQCALQHACGeBAIAoAcAIZ8EgAAAAAGgBAIAoAcAIaEEIACyBwAhogRAALEHACGjBAEAnwcAIaQEQACzBwAhpQRAALMHACGmBAIAtAcAIR8OAAC1BwAgDwAAuwcAIBAAALYHACARAAC3BwAgEwAAuQcAIBQAALoHACAWAAC8BwAggAQCAKAHACGBBAEAngcAIZEEAQCfBwAhkgQBAJ8HACGTBAEAnwcAIZQEAgCgBwAhlQQCAKAHACGWBAIAtAcAIZcEAQCeBwAhmAQBAJ4HACGZBAEAnwcAIZoEQACxBwAhmwQBAJ4HACGcBEAAsQcAIZ0EAgC0BwAhngQCAKAHACGfBIAAAAABoAQCAKAHACGhBCAAsgcAIaIEQACxBwAhowQBAJ8HACGkBEAAswcAIaUEQACzBwAhpgQCALQHACEDAAAAJAAgKgAA9gsAICsAAIgMACANAAAAJAAgDgAA7gcAICMAAIgMACCABAIAoAcAIYEEAQCeBwAhhAQBAJ4HACGRBAEAnwcAIZQEAgCgBwAhoAQCAKAHACGkBEAAswcAIaUEQACzBwAhrQQBAJ8HACGuBAEAngcAIQsOAADuBwAggAQCAKAHACGBBAEAngcAIYQEAQCeBwAhkQQBAJ8HACGUBAIAoAcAIaAEAgCgBwAhpARAALMHACGlBEAAswcAIa0EAQCfBwAhrgQBAJ4HACEDAAAAHAAgKgAA-AsAICsAAIsMACASAAAAHAAgDQAAiQgAIA8AAIoIACAYAACLCAAgIwAAiwwAIIAEAgCgBwAhgQQBAJ4HACGEBAEAngcAIaEEIACyBwAhpARAALMHACGlBEAAswcAIbAEAQCeBwAhsQQCAKAHACGyBCAAsgcAIbMEgAAAAAG0BIAAAAABtQQBAJ8HACG2BAEAngcAIRANAACJCAAgDwAAiggAIBgAAIsIACCABAIAoAcAIYEEAQCeBwAhhAQBAJ4HACGhBCAAsgcAIaQEQACzBwAhpQRAALMHACGwBAEAngcAIbEEAgCgBwAhsgQgALIHACGzBIAAAAABtASAAAAAAbUEAQCfBwAhtgQBAJ4HACEOBwAA_QoAIAgAAP4KACALAAD_CgAgDAAAgAsAIBkAAIELACAaAACCCwAgGwAAgwsAIBwAAIQLACCABAIAAAABhAQBAAAAAaQEQAAAAAGlBEAAAAABnwUBAAAAAaAFAQAAAAECAAAAAQAgKgAAjAwAIB8OAADhBwAgEAAA4gcAIBEAAOgHACASAADjBwAgEwAA5AcAIBQAAOUHACAWAADnBwAggAQCAAAAAYEEAQAAAAGRBAEAAAABkgQBAAAAAZMEAQAAAAGUBAIAAAABlQQCAAAAAZYEAgAAAAGXBAEAAAABmAQBAAAAAZkEAQAAAAGaBEAAAAABmwQBAAAAAZwEQAAAAAGdBAIAAAABngQCAAAAAZ8EgAAAAAGgBAIAAAABoQQgAAAAAaIEQAAAAAGjBAEAAAABpARAAAAAAaUEQAAAAAGmBAIAAAABAgAAACoAICoAAI4MACADAAAAGgAgKgAAjAwAICsAAJIMACAQAAAAGgAgBwAAlwoAIAgAAJgKACALAACZCgAgDAAAmgoAIBkAAJsKACAaAACcCgAgGwAAnQoAIBwAAJ4KACAjAACSDAAggAQCAKAHACGEBAEAngcAIaQEQACzBwAhpQRAALMHACGfBQEAngcAIaAFAQCeBwAhDgcAAJcKACAIAACYCgAgCwAAmQoAIAwAAJoKACAZAACbCgAgGgAAnAoAIBsAAJ0KACAcAACeCgAggAQCAKAHACGEBAEAngcAIaQEQACzBwAhpQRAALMHACGfBQEAngcAIaAFAQCeBwAhAwAAACgAICoAAI4MACArAACVDAAgIQAAACgAIA4AALUHACAQAAC2BwAgEQAAtwcAIBIAALgHACATAAC5BwAgFAAAugcAIBYAALwHACAjAACVDAAggAQCAKAHACGBBAEAngcAIZEEAQCfBwAhkgQBAJ8HACGTBAEAnwcAIZQEAgCgBwAhlQQCAKAHACGWBAIAtAcAIZcEAQCeBwAhmAQBAJ4HACGZBAEAnwcAIZoEQACxBwAhmwQBAJ4HACGcBEAAsQcAIZ0EAgC0BwAhngQCAKAHACGfBIAAAAABoAQCAKAHACGhBCAAsgcAIaIEQACxBwAhowQBAJ8HACGkBEAAswcAIaUEQACzBwAhpgQCALQHACEfDgAAtQcAIBAAALYHACARAAC3BwAgEgAAuAcAIBMAALkHACAUAAC6BwAgFgAAvAcAIIAEAgCgBwAhgQQBAJ4HACGRBAEAnwcAIZIEAQCfBwAhkwQBAJ8HACGUBAIAoAcAIZUEAgCgBwAhlgQCALQHACGXBAEAngcAIZgEAQCeBwAhmQQBAJ8HACGaBEAAsQcAIZsEAQCeBwAhnARAALEHACGdBAIAtAcAIZ4EAgCgBwAhnwSAAAAAAaAEAgCgBwAhoQQgALIHACGiBEAAsQcAIaMEAQCfBwAhpARAALMHACGlBEAAswcAIaYEAgC0BwAhHw4AAOEHACAPAADmBwAgEAAA4gcAIBEAAOgHACASAADjBwAgEwAA5AcAIBQAAOUHACCABAIAAAABgQQBAAAAAZEEAQAAAAGSBAEAAAABkwQBAAAAAZQEAgAAAAGVBAIAAAABlgQCAAAAAZcEAQAAAAGYBAEAAAABmQQBAAAAAZoEQAAAAAGbBAEAAAABnARAAAAAAZ0EAgAAAAGeBAIAAAABnwSAAAAAAaAEAgAAAAGhBCAAAAABogRAAAAAAaMEAQAAAAGkBEAAAAABpQRAAAAAAaYEAgAAAAECAAAAKgAgKgAAlgwAIAMAAAAoACAqAACWDAAgKwAAmgwAICEAAAAoACAOAAC1BwAgDwAAuwcAIBAAALYHACARAAC3BwAgEgAAuAcAIBMAALkHACAUAAC6BwAgIwAAmgwAIIAEAgCgBwAhgQQBAJ4HACGRBAEAnwcAIZIEAQCfBwAhkwQBAJ8HACGUBAIAoAcAIZUEAgCgBwAhlgQCALQHACGXBAEAngcAIZgEAQCeBwAhmQQBAJ8HACGaBEAAsQcAIZsEAQCeBwAhnARAALEHACGdBAIAtAcAIZ4EAgCgBwAhnwSAAAAAAaAEAgCgBwAhoQQgALIHACGiBEAAsQcAIaMEAQCfBwAhpARAALMHACGlBEAAswcAIaYEAgC0BwAhHw4AALUHACAPAAC7BwAgEAAAtgcAIBEAALcHACASAAC4BwAgEwAAuQcAIBQAALoHACCABAIAoAcAIYEEAQCeBwAhkQQBAJ8HACGSBAEAnwcAIZMEAQCfBwAhlAQCAKAHACGVBAIAoAcAIZYEAgC0BwAhlwQBAJ4HACGYBAEAngcAIZkEAQCfBwAhmgRAALEHACGbBAEAngcAIZwEQACxBwAhnQQCALQHACGeBAIAoAcAIZ8EgAAAAAGgBAIAoAcAIaEEIACyBwAhogRAALEHACGjBAEAnwcAIaQEQACzBwAhpQRAALMHACGmBAIAtAcAIQoGABIHBgIIDAMLEAUMGQgZHwkaPwobQAwcQQwdQg0DAwABBQoDBgAEAgMAAQQAAgEFCwADAwABBgAHChQGAQkABQEKFQABAxsBBQYAEQ0AAQ8jChc7DBgnCwIDAAEOAAkDBgAQDgAJFysMCQYADw4ACQ8yDRAACxEsDBItDBMuARQAARY2DgIDAAEVAAwBFQAMAw84ABI3ABY5AAEXOgADDzwAFz4AGD0ACQdDAAhEAAtFAAxGABlHABpIABtJABxKAB1LAAAAAAUGABcwABgxABkyABozABsAAAAAAAUGABcwABgxABkyABozABsAAAAFBgAhMAAiMQAjMgAkMwAlAAAAAAAFBgAhMAAiMQAjMgAkMwAlAAAABQYAKzAALDEALTIALjMALwAAAAAABQYAKzAALDEALTIALjMALwIGADNXmQEyAVYAMQFXmgEAAAAFBgA3MAA4MQA5MgA6MwA7AAAAAAAFBgA3MAA4MQA5MgA6MwA7AVYAMQFWADEFBgBAMABBMQBCMgBDMwBEAAAAAAAFBgBAMABBMQBCMgBDMwBEAAAABQYASjAASzEATDIATTMATgAAAAAABQYASjAASzEATDIATTMATgAAAAUGAFQwAFUxAFYyAFczAFgAAAAAAAUGAFQwAFUxAFYyAFczAFgAAAAFBgBeMABfMQBgMgBhMwBiAAAAAAAFBgBeMABfMQBgMgBhMwBiAAAABQYAaDAAaTEAajIAazMAbAAAAAAABQYAaDAAaTEAajIAazMAbAAAAAUGAHIwAHMxAHQyAHUzAHYAAAAAAAUGAHIwAHMxAHQyAHUzAHYAAAAFBgB8MAB9MQB-MgB_MwCAAQAAAAAABQYAfDAAfTEAfjIAfzMAgAEAAAAFBgCGATAAhwExAIgBMgCJATMAigEAAAAAAAUGAIYBMACHATEAiAEyAIkBMwCKAQAAAAUGAJABMACRATEAkgEyAJMBMwCUAQAAAAAABQYAkAEwAJEBMQCSATIAkwEzAJQBAAAABQYAmgEwAJsBMQCcATIAnQEzAJ4BAAAAAAAFBgCaATAAmwExAJwBMgCdATMAngECBgClAYwCrgOhAQQGAKQBiAIAoAGKArIDogGLArYDowEBiQIAoQEBiQIAoQECigK3AwCLArgDAAGMArkDAAAABQYAqQEwAKoBMQCrATIArAEzAK0BAAAAAAAFBgCpATAAqgExAKsBMgCsATMArQEBiAIAoAEBiAIAoAEFBgCyATAAswExALQBMgC1ATMAtgEAAAAAAAUGALIBMACzATEAtAEyALUBMwC2AQGJAgChAQGJAgChAQUGALsBMAC8ATEAvQEyAL4BMwC_AQAAAAAABQYAuwEwALwBMQC9ATIAvgEzAL8BAYkCAKEBAYkCAKEBBQYAxAEwAMUBMQDGATIAxwEzAMgBAAAAAAAFBgDEATAAxQExAMYBMgDHATMAyAEBAwABAQMAAQUGAM0BMADOATEAzwEyANABMwDRAQAAAAAABQYAzQEwAM4BMQDPATIA0AEzANEBAgMAAQQAAgIDAAEEAAIFBgDWATAA1wExANgBMgDZATMA2gEAAAAAAAUGANYBMADXATEA2AEyANkBMwDaAQEDAAEBAwABBQYA3wEwAOABMQDhATIA4gEzAOMBAAAAAAAFBgDfATAA4AExAOEBMgDiATMA4wEBCQAFAQkABQUGAOgBMADpATEA6gEyAOsBMwDsAQAAAAAABQYA6AEwAOkBMQDqATIA6wEzAOwBAQP1BAEBA_sEAQUGAPEBMADyATEA8wEyAPQBMwD1AQAAAAAABQYA8QEwAPIBMQDzATIA9AEzAPUBAQ0AAQENAAEFBgD6ATAA-wExAPwBMgD9ATMA_gEAAAAAAAUGAPoBMAD7ATEA_AEyAP0BMwD-AQIDAAEOAAkCAwABDgAJBQYAgwIwAIQCMQCFAjIAhgIzAIcCAAAAAAAFBgCDAjAAhAIxAIUCMgCGAjMAhwIBDgAJAQ4ACQUGAIwCMACNAjEAjgIyAI8CMwCQAgAAAAAABQYAjAIwAI0CMQCOAjIAjwIzAJACBQ4ACRAACxHPBQwT0AUBFAABBQ4ACRAACxHWBQwT1wUBFAABBQYAlQIwAJYCMQCXAjIAmAIzAJkCAAAAAAAFBgCVAjAAlgIxAJcCMgCYAjMAmQICAwABFQAMAgMAARUADAUGAJ4CMACfAjEAoAIyAKECMwCiAgAAAAAABQYAngIwAJ8CMQCgAjIAoQIzAKICARUADAEVAAwFBgCnAjAAqAIxAKkCMgCqAjMAqwIAAAAAAAUGAKcCMACoAjEAqQIyAKoCMwCrAh4CAR9MASBOASFPASJQASRSASVUEyZVFCdXAShZEylaFSxbAS1cAS5dEzRgFjVhHDZjHTdkHThnHTloHTppHTtrHTxtEz1uHj5wHT9yE0BzH0F0HUJ1HUN2E0R5IEV6JkZ8J0d9J0iAASdJgQEnSoIBJ0uEASdMhgETTYcBKE6JASdPiwETUIwBKVGNASdSjgEnU48BE1SSASpVkwEwWJUBMVmbATFangExW58BMVygATFdogExXqQBE1-lATRgpwExYakBE2KqATVjqwExZKwBMWWtARNmsAE2Z7EBPGiyATJpswEyarQBMmu1ATJstgEybbgBMm66ARNvuwE9cL0BMnG_ARNywAE-c8EBMnTCATJ1wwETdsYBP3fHAUV4yQFGecoBRnrNAUZ7zgFGfM8BRn3RAUZ-0wETf9QBR4AB1gFGgQHYAROCAdkBSIMB2gFGhAHbAUaFAdwBE4YB3wFJhwHgAU-IAeIBUIkB4wFQigHmAVCLAecBUIwB6AFQjQHqAVCOAewBE48B7QFRkAHvAVCRAfEBE5IB8gFSkwHzAVCUAfQBUJUB9QETlgH4AVOXAfkBWZgB-wFamQH8AVqaAf8BWpsBgAJanAGBAlqdAYMCWp4BhQITnwGGAlugAYgCWqEBigITogGLAlyjAYwCWqQBjQJapQGOAhOmAZECXacBkgJjqAGUAmSpAZUCZKoBmAJkqwGZAmSsAZoCZK0BnAJkrgGeAhOvAZ8CZbABoQJksQGjAhOyAaQCZrMBpQJktAGmAmS1AacCE7YBqgJntwGrAm24Aa0CbrkBrgJuugGxAm67AbICbrwBswJuvQG1Am6-AbcCE78BuAJvwAG6Am7BAbwCE8IBvQJwwwG-Am7EAb8CbsUBwAITxgHDAnHHAcQCd8gBxgJ4yQHHAnjKAcoCeMsBywJ4zAHMAnjNAc4CeM4B0AITzwHRAnnQAdMCeNEB1QIT0gHWAnrTAdcCeNQB2AJ41QHZAhPWAdwCe9cB3QKBAdgB3wKCAdkB4AKCAdoB4wKCAdsB5AKCAdwB5QKCAd0B5wKCAd4B6QIT3wHqAoMB4AHsAoIB4QHuAhPiAe8ChAHjAfACggHkAfECggHlAfICE-YB9QKFAecB9gKLAegB-AKMAekB-QKMAeoB_AKMAesB_QKMAewB_gKMAe0BgAOMAe4BggMT7wGDA40B8AGFA4wB8QGHAxPyAYgDjgHzAYkDjAH0AYoDjAH1AYsDE_YBjgOPAfcBjwOVAfgBkQOWAfkBkgOWAfoBlQOWAfsBlgOWAfwBlwOWAf0BmQOWAf4BmwMT_wGcA5cBgAKeA5YBgQKgAxOCAqEDmAGDAqIDlgGEAqMDlgGFAqQDE4YCpwOZAYcCqAOfAY0CqgOgAY4CugOgAY8CvQOgAZACvgOgAZECvwOgAZICwQOgAZMCwwMTlALEA6YBlQLGA6ABlgLIAxOXAskDpwGYAsoDoAGZAssDoAGaAswDE5sCzwOoAZwC0AOuAZ0C0QOhAZ4C0gOhAZ8C0wOhAaAC1AOhAaEC1QOhAaIC1wOhAaMC2QMTpALaA68BpQLcA6EBpgLeAxOnAt8DsAGoAuADoQGpAuEDoQGqAuIDE6sC5QOxAawC5gO3Aa0C5wOiAa4C6AOiAa8C6QOiAbAC6gOiAbEC6wOiAbIC7QOiAbMC7wMTtALwA7gBtQLyA6IBtgL0AxO3AvUDuQG4AvYDogG5AvcDogG6AvgDE7sC-wO6AbwC_APAAb0C_QOjAb4C_gOjAb8C_wOjAcACgASjAcECgQSjAcICgwSjAcMChQQTxAKGBMEBxQKIBKMBxgKKBBPHAosEwgHIAowEowHJAo0EowHKAo4EE8sCkQTDAcwCkgTJAc0CkwQCzgKUBALPApUEAtAClgQC0QKXBALSApkEAtMCmwQT1AKcBMoB1QKeBALWAqAEE9cCoQTLAdgCogQC2QKjBALaAqQEE9sCpwTMAdwCqATSAd0CqQQD3gKqBAPfAqsEA-ACrAQD4QKtBAPiAq8EA-MCsQQT5AKyBNMB5QK0BAPmArYEE-cCtwTUAegCuAQD6QK5BAPqAroEE-sCvQTVAewCvgTbAe0CvwQF7gLABAXvAsEEBfACwgQF8QLDBAXyAsUEBfMCxwQT9ALIBNwB9QLKBAX2AswEE_cCzQTdAfgCzgQF-QLPBAX6AtAEE_sC0wTeAfwC1ATkAf0C1QQG_gLWBAb_AtcEBoAD2AQGgQPZBAaCA9sEBoMD3QQThAPeBOUBhQPgBAaGA-IEE4cD4wTmAYgD5AQGiQPlBAaKA-YEE4sD6QTnAYwD6gTtAY0D6wQIjgPsBAiPA-0ECJAD7gQIkQPvBAiSA_EECJMD8wQTlAP0BO4BlQP3BAiWA_kEE5cD-gTvAZgD_AQImQP9BAiaA_4EE5sDgQXwAZwDggX2AZ0DgwUJngOEBQmfA4UFCaADhgUJoQOHBQmiA4kFCaMDiwUTpAOMBfcBpQOOBQmmA5AFE6cDkQX4AagDkgUJqQOTBQmqA5QFE6sDlwX5AawDmAX_Aa0DmQUKrgOaBQqvA5sFCrADnAUKsQOdBQqyA58FCrMDoQUTtAOiBYACtQOkBQq2A6YFE7cDpwWBArgDqAUKuQOpBQq6A6oFE7sDrQWCArwDrgWIAr0DrwULvgOwBQu_A7EFC8ADsgULwQOzBQvCA7UFC8MDtwUTxAO4BYkCxQO6BQvGA7wFE8cDvQWKAsgDvgULyQO_BQvKA8AFE8sDwwWLAswDxAWRAs0DxQUMzgPGBQzPA8cFDNADyAUM0QPJBQzSA8sFDNMDzQUT1APOBZIC1QPSBQzWA9QFE9cD1QWTAtgD2AUM2QPZBQzaA9oFE9sD3QWUAtwD3gWaAt0D3wUN3gPgBQ3fA-EFDeAD4gUN4QPjBQ3iA-UFDeMD5wUT5APoBZsC5QPqBQ3mA-wFE-cD7QWcAugD7gUN6QPvBQ3qA_AFE-sD8wWdAuwD9AWjAu0D9QUO7gP2BQ7vA_cFDvAD-AUO8QP5BQ7yA_sFDvMD_QUT9AP-BaQC9QOABg72A4IGE_cDgwalAvgDhAYO-QOFBg76A4YGE_sDiQamAvwDigasAg" } async function decodeBase64AsWasm(wasmBase64: string): Promise { @@ -45,10 +45,10 @@ async function decodeBase64AsWasm(wasmBase64: string): Promise await import("@prisma/client/runtime/query_compiler_fast_bg.mysql.mjs"), + getRuntime: async () => await import("@prisma/client/runtime/query_compiler_fast_bg.postgresql.mjs"), getQueryCompilerWasmModule: async () => { - const { wasm } = await import("@prisma/client/runtime/query_compiler_fast_bg.mysql.wasm-base64.mjs") + const { wasm } = await import("@prisma/client/runtime/query_compiler_fast_bg.postgresql.wasm-base64.mjs") return await decodeBase64AsWasm(wasm) }, @@ -367,6 +367,116 @@ export interface PrismaClient< * ``` */ get mihomeEnvHour(): Prisma.MihomeEnvHourDelegate; + + /** + * `prisma.habit`: Exposes CRUD operations for the **Habit** model. + * Example usage: + * ```ts + * // Fetch zero or more Habits + * const habits = await prisma.habit.findMany() + * ``` + */ + get habit(): Prisma.HabitDelegate; + + /** + * `prisma.checkIn`: Exposes CRUD operations for the **CheckIn** model. + * Example usage: + * ```ts + * // Fetch zero or more CheckIns + * const checkIns = await prisma.checkIn.findMany() + * ``` + */ + get checkIn(): Prisma.CheckInDelegate; + + /** + * `prisma.checkList`: Exposes CRUD operations for the **CheckList** model. + * Example usage: + * ```ts + * // Fetch zero or more CheckLists + * const checkLists = await prisma.checkList.findMany() + * ``` + */ + get checkList(): Prisma.CheckListDelegate; + + /** + * `prisma.checkListItem`: Exposes CRUD operations for the **CheckListItem** model. + * Example usage: + * ```ts + * // Fetch zero or more CheckListItems + * const checkListItems = await prisma.checkListItem.findMany() + * ``` + */ + get checkListItem(): Prisma.CheckListItemDelegate; + + /** + * `prisma.checkListTemplate`: Exposes CRUD operations for the **CheckListTemplate** model. + * Example usage: + * ```ts + * // Fetch zero or more CheckListTemplates + * const checkListTemplates = await prisma.checkListTemplate.findMany() + * ``` + */ + get checkListTemplate(): Prisma.CheckListTemplateDelegate; + + /** + * `prisma.project`: Exposes CRUD operations for the **Project** model. + * Example usage: + * ```ts + * // Fetch zero or more Projects + * const projects = await prisma.project.findMany() + * ``` + */ + get project(): Prisma.ProjectDelegate; + + /** + * `prisma.projectMember`: Exposes CRUD operations for the **ProjectMember** model. + * Example usage: + * ```ts + * // Fetch zero or more ProjectMembers + * const projectMembers = await prisma.projectMember.findMany() + * ``` + */ + get projectMember(): Prisma.ProjectMemberDelegate; + + /** + * `prisma.boardColumn`: Exposes CRUD operations for the **BoardColumn** model. + * Example usage: + * ```ts + * // Fetch zero or more BoardColumns + * const boardColumns = await prisma.boardColumn.findMany() + * ``` + */ + get boardColumn(): Prisma.BoardColumnDelegate; + + /** + * `prisma.task`: Exposes CRUD operations for the **Task** model. + * Example usage: + * ```ts + * // Fetch zero or more Tasks + * const tasks = await prisma.task.findMany() + * ``` + */ + get task(): Prisma.TaskDelegate; + + /** + * `prisma.taskMember`: Exposes CRUD operations for the **TaskMember** model. + * Example usage: + * ```ts + * // Fetch zero or more TaskMembers + * const taskMembers = await prisma.taskMember.findMany() + * ``` + */ + get taskMember(): Prisma.TaskMemberDelegate; + + /** + * `prisma.taskTag`: Exposes CRUD operations for the **TaskTag** model. + * Example usage: + * ```ts + * // Fetch zero or more TaskTags + * const taskTags = await prisma.taskTag.findMany() + * ``` + */ + get taskTag(): Prisma.TaskTagDelegate; } export function getPrismaClientClass(): PrismaClientConstructor { diff --git a/generated/prisma/internal/prismaNamespace.ts b/generated/prisma/internal/prismaNamespace.ts index 2a1166a..ab66c1f 100644 --- a/generated/prisma/internal/prismaNamespace.ts +++ b/generated/prisma/internal/prismaNamespace.ts @@ -401,7 +401,18 @@ export const ModelName = { MihomeHome: 'MihomeHome', MihomeDevice: 'MihomeDevice', MihomeEnvSample: 'MihomeEnvSample', - MihomeEnvHour: 'MihomeEnvHour' + MihomeEnvHour: 'MihomeEnvHour', + Habit: 'Habit', + CheckIn: 'CheckIn', + CheckList: 'CheckList', + CheckListItem: 'CheckListItem', + CheckListTemplate: 'CheckListTemplate', + Project: 'Project', + ProjectMember: 'ProjectMember', + BoardColumn: 'BoardColumn', + Task: 'Task', + TaskMember: 'TaskMember', + TaskTag: 'TaskTag' } as const export type ModelName = (typeof ModelName)[keyof typeof ModelName] @@ -417,7 +428,7 @@ export type TypeMap result: BatchPayload } + createManyAndReturn: { + args: Prisma.UserCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } delete: { args: Prisma.UserDeleteArgs result: runtime.Types.Utils.PayloadToResult @@ -469,6 +484,10 @@ export type TypeMap result: BatchPayload } + updateManyAndReturn: { + args: Prisma.UserUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } upsert: { args: Prisma.UserUpsertArgs result: runtime.Types.Utils.PayloadToResult @@ -519,6 +538,10 @@ export type TypeMap result: BatchPayload } + createManyAndReturn: { + args: Prisma.ArticleCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } delete: { args: Prisma.ArticleDeleteArgs result: runtime.Types.Utils.PayloadToResult @@ -535,6 +558,10 @@ export type TypeMap result: BatchPayload } + updateManyAndReturn: { + args: Prisma.ArticleUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } upsert: { args: Prisma.ArticleUpsertArgs result: runtime.Types.Utils.PayloadToResult @@ -585,6 +612,10 @@ export type TypeMap result: BatchPayload } + createManyAndReturn: { + args: Prisma.StrategyCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } delete: { args: Prisma.StrategyDeleteArgs result: runtime.Types.Utils.PayloadToResult @@ -601,6 +632,10 @@ export type TypeMap result: BatchPayload } + updateManyAndReturn: { + args: Prisma.StrategyUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } upsert: { args: Prisma.StrategyUpsertArgs result: runtime.Types.Utils.PayloadToResult @@ -651,6 +686,10 @@ export type TypeMap result: BatchPayload } + createManyAndReturn: { + args: Prisma.IndicatorCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } delete: { args: Prisma.IndicatorDeleteArgs result: runtime.Types.Utils.PayloadToResult @@ -667,6 +706,10 @@ export type TypeMap result: BatchPayload } + updateManyAndReturn: { + args: Prisma.IndicatorUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } upsert: { args: Prisma.IndicatorUpsertArgs result: runtime.Types.Utils.PayloadToResult @@ -717,6 +760,10 @@ export type TypeMap result: BatchPayload } + createManyAndReturn: { + args: Prisma.IndicatorPointCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } delete: { args: Prisma.IndicatorPointDeleteArgs result: runtime.Types.Utils.PayloadToResult @@ -733,6 +780,10 @@ export type TypeMap result: BatchPayload } + updateManyAndReturn: { + args: Prisma.IndicatorPointUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } upsert: { args: Prisma.IndicatorPointUpsertArgs result: runtime.Types.Utils.PayloadToResult @@ -783,6 +834,10 @@ export type TypeMap result: BatchPayload } + createManyAndReturn: { + args: Prisma.NewsCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } delete: { args: Prisma.NewsDeleteArgs result: runtime.Types.Utils.PayloadToResult @@ -799,6 +854,10 @@ export type TypeMap result: BatchPayload } + updateManyAndReturn: { + args: Prisma.NewsUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } upsert: { args: Prisma.NewsUpsertArgs result: runtime.Types.Utils.PayloadToResult @@ -849,6 +908,10 @@ export type TypeMap result: BatchPayload } + createManyAndReturn: { + args: Prisma.AlertCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } delete: { args: Prisma.AlertDeleteArgs result: runtime.Types.Utils.PayloadToResult @@ -865,6 +928,10 @@ export type TypeMap result: BatchPayload } + updateManyAndReturn: { + args: Prisma.AlertUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } upsert: { args: Prisma.AlertUpsertArgs result: runtime.Types.Utils.PayloadToResult @@ -915,6 +982,10 @@ export type TypeMap result: BatchPayload } + createManyAndReturn: { + args: Prisma.ServerNodeCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } delete: { args: Prisma.ServerNodeDeleteArgs result: runtime.Types.Utils.PayloadToResult @@ -931,6 +1002,10 @@ export type TypeMap result: BatchPayload } + updateManyAndReturn: { + args: Prisma.ServerNodeUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } upsert: { args: Prisma.ServerNodeUpsertArgs result: runtime.Types.Utils.PayloadToResult @@ -981,6 +1056,10 @@ export type TypeMap result: BatchPayload } + createManyAndReturn: { + args: Prisma.ServerNodeHistoryCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } delete: { args: Prisma.ServerNodeHistoryDeleteArgs result: runtime.Types.Utils.PayloadToResult @@ -997,6 +1076,10 @@ export type TypeMap result: BatchPayload } + updateManyAndReturn: { + args: Prisma.ServerNodeHistoryUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } upsert: { args: Prisma.ServerNodeHistoryUpsertArgs result: runtime.Types.Utils.PayloadToResult @@ -1047,6 +1130,10 @@ export type TypeMap result: BatchPayload } + createManyAndReturn: { + args: Prisma.ServiceCheckCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } delete: { args: Prisma.ServiceCheckDeleteArgs result: runtime.Types.Utils.PayloadToResult @@ -1063,6 +1150,10 @@ export type TypeMap result: BatchPayload } + updateManyAndReturn: { + args: Prisma.ServiceCheckUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } upsert: { args: Prisma.ServiceCheckUpsertArgs result: runtime.Types.Utils.PayloadToResult @@ -1113,6 +1204,10 @@ export type TypeMap result: BatchPayload } + createManyAndReturn: { + args: Prisma.Econ_SowInventoryCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } delete: { args: Prisma.Econ_SowInventoryDeleteArgs result: runtime.Types.Utils.PayloadToResult @@ -1129,6 +1224,10 @@ export type TypeMap result: BatchPayload } + updateManyAndReturn: { + args: Prisma.Econ_SowInventoryUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } upsert: { args: Prisma.Econ_SowInventoryUpsertArgs result: runtime.Types.Utils.PayloadToResult @@ -1179,6 +1278,10 @@ export type TypeMap result: BatchPayload } + createManyAndReturn: { + args: Prisma.ScraperCacheCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } delete: { args: Prisma.ScraperCacheDeleteArgs result: runtime.Types.Utils.PayloadToResult @@ -1195,6 +1298,10 @@ export type TypeMap result: BatchPayload } + updateManyAndReturn: { + args: Prisma.ScraperCacheUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } upsert: { args: Prisma.ScraperCacheUpsertArgs result: runtime.Types.Utils.PayloadToResult @@ -1245,6 +1352,10 @@ export type TypeMap result: BatchPayload } + createManyAndReturn: { + args: Prisma.OilPriceHistoryCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } delete: { args: Prisma.OilPriceHistoryDeleteArgs result: runtime.Types.Utils.PayloadToResult @@ -1261,6 +1372,10 @@ export type TypeMap result: BatchPayload } + updateManyAndReturn: { + args: Prisma.OilPriceHistoryUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } upsert: { args: Prisma.OilPriceHistoryUpsertArgs result: runtime.Types.Utils.PayloadToResult @@ -1311,6 +1426,10 @@ export type TypeMap result: BatchPayload } + createManyAndReturn: { + args: Prisma.MihomeAccountCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } delete: { args: Prisma.MihomeAccountDeleteArgs result: runtime.Types.Utils.PayloadToResult @@ -1327,6 +1446,10 @@ export type TypeMap result: BatchPayload } + updateManyAndReturn: { + args: Prisma.MihomeAccountUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } upsert: { args: Prisma.MihomeAccountUpsertArgs result: runtime.Types.Utils.PayloadToResult @@ -1377,6 +1500,10 @@ export type TypeMap result: BatchPayload } + createManyAndReturn: { + args: Prisma.MihomeHomeCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } delete: { args: Prisma.MihomeHomeDeleteArgs result: runtime.Types.Utils.PayloadToResult @@ -1393,6 +1520,10 @@ export type TypeMap result: BatchPayload } + updateManyAndReturn: { + args: Prisma.MihomeHomeUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } upsert: { args: Prisma.MihomeHomeUpsertArgs result: runtime.Types.Utils.PayloadToResult @@ -1443,6 +1574,10 @@ export type TypeMap result: BatchPayload } + createManyAndReturn: { + args: Prisma.MihomeDeviceCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } delete: { args: Prisma.MihomeDeviceDeleteArgs result: runtime.Types.Utils.PayloadToResult @@ -1459,6 +1594,10 @@ export type TypeMap result: BatchPayload } + updateManyAndReturn: { + args: Prisma.MihomeDeviceUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } upsert: { args: Prisma.MihomeDeviceUpsertArgs result: runtime.Types.Utils.PayloadToResult @@ -1509,6 +1648,10 @@ export type TypeMap result: BatchPayload } + createManyAndReturn: { + args: Prisma.MihomeEnvSampleCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } delete: { args: Prisma.MihomeEnvSampleDeleteArgs result: runtime.Types.Utils.PayloadToResult @@ -1525,6 +1668,10 @@ export type TypeMap result: BatchPayload } + updateManyAndReturn: { + args: Prisma.MihomeEnvSampleUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } upsert: { args: Prisma.MihomeEnvSampleUpsertArgs result: runtime.Types.Utils.PayloadToResult @@ -1575,6 +1722,10 @@ export type TypeMap result: BatchPayload } + createManyAndReturn: { + args: Prisma.MihomeEnvHourCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } delete: { args: Prisma.MihomeEnvHourDeleteArgs result: runtime.Types.Utils.PayloadToResult @@ -1591,6 +1742,10 @@ export type TypeMap result: BatchPayload } + updateManyAndReturn: { + args: Prisma.MihomeEnvHourUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } upsert: { args: Prisma.MihomeEnvHourUpsertArgs result: runtime.Types.Utils.PayloadToResult @@ -1609,6 +1764,820 @@ export type TypeMap + fields: Prisma.HabitFieldRefs + operations: { + findUnique: { + args: Prisma.HabitFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.HabitFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.HabitFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.HabitFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.HabitFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.HabitCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.HabitCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.HabitCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.HabitDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.HabitUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.HabitDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.HabitUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.HabitUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.HabitUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.HabitAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.HabitGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.HabitCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + CheckIn: { + payload: Prisma.$CheckInPayload + fields: Prisma.CheckInFieldRefs + operations: { + findUnique: { + args: Prisma.CheckInFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.CheckInFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.CheckInFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.CheckInFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.CheckInFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.CheckInCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.CheckInCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.CheckInCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.CheckInDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.CheckInUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.CheckInDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.CheckInUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.CheckInUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.CheckInUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.CheckInAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.CheckInGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.CheckInCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + CheckList: { + payload: Prisma.$CheckListPayload + fields: Prisma.CheckListFieldRefs + operations: { + findUnique: { + args: Prisma.CheckListFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.CheckListFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.CheckListFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.CheckListFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.CheckListFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.CheckListCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.CheckListCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.CheckListCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.CheckListDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.CheckListUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.CheckListDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.CheckListUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.CheckListUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.CheckListUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.CheckListAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.CheckListGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.CheckListCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + CheckListItem: { + payload: Prisma.$CheckListItemPayload + fields: Prisma.CheckListItemFieldRefs + operations: { + findUnique: { + args: Prisma.CheckListItemFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.CheckListItemFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.CheckListItemFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.CheckListItemFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.CheckListItemFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.CheckListItemCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.CheckListItemCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.CheckListItemCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.CheckListItemDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.CheckListItemUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.CheckListItemDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.CheckListItemUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.CheckListItemUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.CheckListItemUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.CheckListItemAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.CheckListItemGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.CheckListItemCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + CheckListTemplate: { + payload: Prisma.$CheckListTemplatePayload + fields: Prisma.CheckListTemplateFieldRefs + operations: { + findUnique: { + args: Prisma.CheckListTemplateFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.CheckListTemplateFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.CheckListTemplateFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.CheckListTemplateFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.CheckListTemplateFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.CheckListTemplateCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.CheckListTemplateCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.CheckListTemplateCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.CheckListTemplateDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.CheckListTemplateUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.CheckListTemplateDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.CheckListTemplateUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.CheckListTemplateUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.CheckListTemplateUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.CheckListTemplateAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.CheckListTemplateGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.CheckListTemplateCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + Project: { + payload: Prisma.$ProjectPayload + fields: Prisma.ProjectFieldRefs + operations: { + findUnique: { + args: Prisma.ProjectFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.ProjectFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.ProjectFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.ProjectFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.ProjectFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.ProjectCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.ProjectCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.ProjectCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.ProjectDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.ProjectUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.ProjectDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.ProjectUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.ProjectUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.ProjectUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.ProjectAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.ProjectGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.ProjectCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + ProjectMember: { + payload: Prisma.$ProjectMemberPayload + fields: Prisma.ProjectMemberFieldRefs + operations: { + findUnique: { + args: Prisma.ProjectMemberFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.ProjectMemberFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.ProjectMemberFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.ProjectMemberFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.ProjectMemberFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.ProjectMemberCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.ProjectMemberCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.ProjectMemberCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.ProjectMemberDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.ProjectMemberUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.ProjectMemberDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.ProjectMemberUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.ProjectMemberUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.ProjectMemberUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.ProjectMemberAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.ProjectMemberGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.ProjectMemberCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + BoardColumn: { + payload: Prisma.$BoardColumnPayload + fields: Prisma.BoardColumnFieldRefs + operations: { + findUnique: { + args: Prisma.BoardColumnFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.BoardColumnFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.BoardColumnFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.BoardColumnFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.BoardColumnFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.BoardColumnCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.BoardColumnCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.BoardColumnCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.BoardColumnDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.BoardColumnUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.BoardColumnDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.BoardColumnUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.BoardColumnUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.BoardColumnUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.BoardColumnAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.BoardColumnGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.BoardColumnCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + Task: { + payload: Prisma.$TaskPayload + fields: Prisma.TaskFieldRefs + operations: { + findUnique: { + args: Prisma.TaskFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.TaskFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.TaskFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.TaskFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.TaskFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.TaskCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.TaskCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.TaskCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.TaskDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.TaskUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.TaskDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.TaskUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.TaskUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.TaskUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.TaskAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.TaskGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.TaskCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + TaskMember: { + payload: Prisma.$TaskMemberPayload + fields: Prisma.TaskMemberFieldRefs + operations: { + findUnique: { + args: Prisma.TaskMemberFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.TaskMemberFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.TaskMemberFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.TaskMemberFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.TaskMemberFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.TaskMemberCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.TaskMemberCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.TaskMemberCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.TaskMemberDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.TaskMemberUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.TaskMemberDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.TaskMemberUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.TaskMemberUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.TaskMemberUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.TaskMemberAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.TaskMemberGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.TaskMemberCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + TaskTag: { + payload: Prisma.$TaskTagPayload + fields: Prisma.TaskTagFieldRefs + operations: { + findUnique: { + args: Prisma.TaskTagFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.TaskTagFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.TaskTagFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.TaskTagFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.TaskTagFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.TaskTagCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.TaskTagCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.TaskTagCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.TaskTagDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.TaskTagUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.TaskTagDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.TaskTagUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.TaskTagUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.TaskTagUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.TaskTagAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.TaskTagGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.TaskTagCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } } } & { other: { @@ -1924,6 +2893,181 @@ export const MihomeEnvHourScalarFieldEnum = { export type MihomeEnvHourScalarFieldEnum = (typeof MihomeEnvHourScalarFieldEnum)[keyof typeof MihomeEnvHourScalarFieldEnum] +export const HabitScalarFieldEnum = { + id: 'id', + userId: 'userId', + name: 'name', + period: 'period', + target: 'target', + color: 'color', + icon: 'icon', + note: 'note', + archived: 'archived', + sortOrder: 'sortOrder', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type HabitScalarFieldEnum = (typeof HabitScalarFieldEnum)[keyof typeof HabitScalarFieldEnum] + + +export const CheckInScalarFieldEnum = { + id: 'id', + habitId: 'habitId', + userId: 'userId', + date: 'date', + seq: 'seq', + note: 'note', + source: 'source', + createdAt: 'createdAt' +} as const + +export type CheckInScalarFieldEnum = (typeof CheckInScalarFieldEnum)[keyof typeof CheckInScalarFieldEnum] + + +export const CheckListScalarFieldEnum = { + id: 'id', + userId: 'userId', + title: 'title', + scene: 'scene', + note: 'note', + archived: 'archived', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type CheckListScalarFieldEnum = (typeof CheckListScalarFieldEnum)[keyof typeof CheckListScalarFieldEnum] + + +export const CheckListItemScalarFieldEnum = { + id: 'id', + listId: 'listId', + title: 'title', + note: 'note', + done: 'done', + doneAt: 'doneAt', + sortOrder: 'sortOrder', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type CheckListItemScalarFieldEnum = (typeof CheckListItemScalarFieldEnum)[keyof typeof CheckListItemScalarFieldEnum] + + +export const CheckListTemplateScalarFieldEnum = { + id: 'id', + userId: 'userId', + name: 'name', + scene: 'scene', + items: 'items', + builtin: 'builtin', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type CheckListTemplateScalarFieldEnum = (typeof CheckListTemplateScalarFieldEnum)[keyof typeof CheckListTemplateScalarFieldEnum] + + +export const ProjectScalarFieldEnum = { + id: 'id', + uuid: 'uuid', + code: 'code', + name: 'name', + ownerId: 'ownerId', + isPersonal: 'isPersonal', + archived: 'archived', + notes: 'notes', + taskKinds: 'taskKinds', + icon: 'icon', + autoArchive: 'autoArchive', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type ProjectScalarFieldEnum = (typeof ProjectScalarFieldEnum)[keyof typeof ProjectScalarFieldEnum] + + +export const ProjectMemberScalarFieldEnum = { + id: 'id', + uuid: 'uuid', + projectUuid: 'projectUuid', + projectId: 'projectId', + userId: 'userId', + permission: 'permission', + createdAt: 'createdAt' +} as const + +export type ProjectMemberScalarFieldEnum = (typeof ProjectMemberScalarFieldEnum)[keyof typeof ProjectMemberScalarFieldEnum] + + +export const BoardColumnScalarFieldEnum = { + id: 'id', + uuid: 'uuid', + projectUuid: 'projectUuid', + projectId: 'projectId', + name: 'name', + sortOrder: 'sortOrder', + boundStatus: 'boundStatus', + retention: 'retention', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type BoardColumnScalarFieldEnum = (typeof BoardColumnScalarFieldEnum)[keyof typeof BoardColumnScalarFieldEnum] + + +export const TaskScalarFieldEnum = { + id: 'id', + uuid: 'uuid', + projectUuid: 'projectUuid', + columnUuid: 'columnUuid', + parentUuid: 'parentUuid', + projectId: 'projectId', + columnId: 'columnId', + parentId: 'parentId', + title: 'title', + description: 'description', + kind: 'kind', + markDate: 'markDate', + status: 'status', + doneAt: 'doneAt', + assigneeId: 'assigneeId', + creatorId: 'creatorId', + extras: 'extras', + sortOrder: 'sortOrder', + archived: 'archived', + archivedAt: 'archivedAt', + archiveSource: 'archiveSource', + createdAt: 'createdAt', + updatedAt: 'updatedAt', + number: 'number' +} as const + +export type TaskScalarFieldEnum = (typeof TaskScalarFieldEnum)[keyof typeof TaskScalarFieldEnum] + + +export const TaskMemberScalarFieldEnum = { + id: 'id', + uuid: 'uuid', + taskUuid: 'taskUuid', + taskId: 'taskId', + userId: 'userId' +} as const + +export type TaskMemberScalarFieldEnum = (typeof TaskMemberScalarFieldEnum)[keyof typeof TaskMemberScalarFieldEnum] + + +export const TaskTagScalarFieldEnum = { + id: 'id', + uuid: 'uuid', + taskUuid: 'taskUuid', + taskId: 'taskId', + name: 'name' +} as const + +export type TaskTagScalarFieldEnum = (typeof TaskTagScalarFieldEnum)[keyof typeof TaskTagScalarFieldEnum] + + export const SortOrder = { asc: 'asc', desc: 'desc' @@ -1947,23 +3091,12 @@ export const NullableJsonNullValueInput = { export type NullableJsonNullValueInput = (typeof NullableJsonNullValueInput)[keyof typeof NullableJsonNullValueInput] -export const UserOrderByRelevanceFieldEnum = { - name: 'name', - email: 'email', - password: 'password' +export const QueryMode = { + default: 'default', + insensitive: 'insensitive' } as const -export type UserOrderByRelevanceFieldEnum = (typeof UserOrderByRelevanceFieldEnum)[keyof typeof UserOrderByRelevanceFieldEnum] - - -export const ArticleOrderByRelevanceFieldEnum = { - title: 'title', - slug: 'slug', - content: 'content', - date: 'date' -} as const - -export type ArticleOrderByRelevanceFieldEnum = (typeof ArticleOrderByRelevanceFieldEnum)[keyof typeof ArticleOrderByRelevanceFieldEnum] +export type QueryMode = (typeof QueryMode)[keyof typeof QueryMode] export const JsonNullValueFilter = { @@ -1975,41 +3108,6 @@ export const JsonNullValueFilter = { export type JsonNullValueFilter = (typeof JsonNullValueFilter)[keyof typeof JsonNullValueFilter] -export const QueryMode = { - default: 'default', - insensitive: 'insensitive' -} as const - -export type QueryMode = (typeof QueryMode)[keyof typeof QueryMode] - - -export const StrategyOrderByRelevanceFieldEnum = { - name: 'name', - description: 'description' -} as const - -export type StrategyOrderByRelevanceFieldEnum = (typeof StrategyOrderByRelevanceFieldEnum)[keyof typeof StrategyOrderByRelevanceFieldEnum] - - -export const IndicatorOrderByRelevanceFieldEnum = { - name: 'name', - groupName: 'groupName', - title: 'title', - unit: 'unit', - source: 'source', - frequency: 'frequency' -} as const - -export type IndicatorOrderByRelevanceFieldEnum = (typeof IndicatorOrderByRelevanceFieldEnum)[keyof typeof IndicatorOrderByRelevanceFieldEnum] - - -export const IndicatorPointOrderByRelevanceFieldEnum = { - period: 'period' -} as const - -export type IndicatorPointOrderByRelevanceFieldEnum = (typeof IndicatorPointOrderByRelevanceFieldEnum)[keyof typeof IndicatorPointOrderByRelevanceFieldEnum] - - export const NullsOrder = { first: 'first', last: 'last' @@ -2018,116 +3116,6 @@ export const NullsOrder = { export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder] -export const NewsOrderByRelevanceFieldEnum = { - title: 'title', - summary: 'summary', - content: 'content', - source: 'source', - url: 'url' -} as const - -export type NewsOrderByRelevanceFieldEnum = (typeof NewsOrderByRelevanceFieldEnum)[keyof typeof NewsOrderByRelevanceFieldEnum] - - -export const AlertOrderByRelevanceFieldEnum = { - indicatorName: 'indicatorName', - period: 'period', - sourceA: 'sourceA', - sourceB: 'sourceB' -} as const - -export type AlertOrderByRelevanceFieldEnum = (typeof AlertOrderByRelevanceFieldEnum)[keyof typeof AlertOrderByRelevanceFieldEnum] - - -export const ServerNodeOrderByRelevanceFieldEnum = { - uuid: 'uuid', - name: 'name', - cpuName: 'cpuName', - os: 'os', - region: 'region', - status: 'status' -} as const - -export type ServerNodeOrderByRelevanceFieldEnum = (typeof ServerNodeOrderByRelevanceFieldEnum)[keyof typeof ServerNodeOrderByRelevanceFieldEnum] - - -export const ServerNodeHistoryOrderByRelevanceFieldEnum = { - uuid: 'uuid' -} as const - -export type ServerNodeHistoryOrderByRelevanceFieldEnum = (typeof ServerNodeHistoryOrderByRelevanceFieldEnum)[keyof typeof ServerNodeHistoryOrderByRelevanceFieldEnum] - - -export const ServiceCheckOrderByRelevanceFieldEnum = { - name: 'name', - url: 'url', - status: 'status', - message: 'message' -} as const - -export type ServiceCheckOrderByRelevanceFieldEnum = (typeof ServiceCheckOrderByRelevanceFieldEnum)[keyof typeof ServiceCheckOrderByRelevanceFieldEnum] - - -export const Econ_SowInventoryOrderByRelevanceFieldEnum = { - month: 'month' -} as const - -export type Econ_SowInventoryOrderByRelevanceFieldEnum = (typeof Econ_SowInventoryOrderByRelevanceFieldEnum)[keyof typeof Econ_SowInventoryOrderByRelevanceFieldEnum] - - -export const ScraperCacheOrderByRelevanceFieldEnum = { - scraperName: 'scraperName', - lastDataHash: 'lastDataHash' -} as const - -export type ScraperCacheOrderByRelevanceFieldEnum = (typeof ScraperCacheOrderByRelevanceFieldEnum)[keyof typeof ScraperCacheOrderByRelevanceFieldEnum] - - -export const OilPriceHistoryOrderByRelevanceFieldEnum = { - province: 'province', - fuel: 'fuel', - source: 'source' -} as const - -export type OilPriceHistoryOrderByRelevanceFieldEnum = (typeof OilPriceHistoryOrderByRelevanceFieldEnum)[keyof typeof OilPriceHistoryOrderByRelevanceFieldEnum] - - -export const MihomeAccountOrderByRelevanceFieldEnum = { - key: 'key', - userId: 'userId', - status: 'status', - lastError: 'lastError' -} as const - -export type MihomeAccountOrderByRelevanceFieldEnum = (typeof MihomeAccountOrderByRelevanceFieldEnum)[keyof typeof MihomeAccountOrderByRelevanceFieldEnum] - - -export const MihomeHomeOrderByRelevanceFieldEnum = { - homeId: 'homeId', - name: 'name' -} as const - -export type MihomeHomeOrderByRelevanceFieldEnum = (typeof MihomeHomeOrderByRelevanceFieldEnum)[keyof typeof MihomeHomeOrderByRelevanceFieldEnum] - - -export const MihomeDeviceOrderByRelevanceFieldEnum = { - did: 'did', - homeId: 'homeId', - model: 'model', - name: 'name', - alias: 'alias' -} as const - -export type MihomeDeviceOrderByRelevanceFieldEnum = (typeof MihomeDeviceOrderByRelevanceFieldEnum)[keyof typeof MihomeDeviceOrderByRelevanceFieldEnum] - - -export const MihomeEnvSampleOrderByRelevanceFieldEnum = { - origin: 'origin' -} as const - -export type MihomeEnvSampleOrderByRelevanceFieldEnum = (typeof MihomeEnvSampleOrderByRelevanceFieldEnum)[keyof typeof MihomeEnvSampleOrderByRelevanceFieldEnum] - - /** * Field references @@ -2141,6 +3129,13 @@ export type IntFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'In +/** + * Reference to a field of type 'Int[]' + */ +export type ListIntFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Int[]'> + + + /** * Reference to a field of type 'String' */ @@ -2148,6 +3143,13 @@ export type StringFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, +/** + * Reference to a field of type 'String[]' + */ +export type ListStringFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'String[]'> + + + /** * Reference to a field of type 'DateTime' */ @@ -2155,6 +3157,13 @@ export type DateTimeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel +/** + * Reference to a field of type 'DateTime[]' + */ +export type ListDateTimeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'DateTime[]'> + + + /** * Reference to a field of type 'Json' */ @@ -2183,12 +3192,26 @@ export type FloatFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, ' +/** + * Reference to a field of type 'Float[]' + */ +export type ListFloatFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Float[]'> + + + /** * Reference to a field of type 'BigInt' */ export type BigIntFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'BigInt'> + +/** + * Reference to a field of type 'BigInt[]' + */ +export type ListBigIntFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'BigInt[]'> + + /** * Batch Payload for updateMany & deleteMany & createMany */ @@ -2302,6 +3325,17 @@ export type GlobalOmitConfig = { mihomeDevice?: Prisma.MihomeDeviceOmit mihomeEnvSample?: Prisma.MihomeEnvSampleOmit mihomeEnvHour?: Prisma.MihomeEnvHourOmit + habit?: Prisma.HabitOmit + checkIn?: Prisma.CheckInOmit + checkList?: Prisma.CheckListOmit + checkListItem?: Prisma.CheckListItemOmit + checkListTemplate?: Prisma.CheckListTemplateOmit + project?: Prisma.ProjectOmit + projectMember?: Prisma.ProjectMemberOmit + boardColumn?: Prisma.BoardColumnOmit + task?: Prisma.TaskOmit + taskMember?: Prisma.TaskMemberOmit + taskTag?: Prisma.TaskTagOmit } /* Types for Logging */ diff --git a/generated/prisma/internal/prismaNamespaceBrowser.ts b/generated/prisma/internal/prismaNamespaceBrowser.ts index 96969ba..251860c 100644 --- a/generated/prisma/internal/prismaNamespaceBrowser.ts +++ b/generated/prisma/internal/prismaNamespaceBrowser.ts @@ -68,7 +68,18 @@ export const ModelName = { MihomeHome: 'MihomeHome', MihomeDevice: 'MihomeDevice', MihomeEnvSample: 'MihomeEnvSample', - MihomeEnvHour: 'MihomeEnvHour' + MihomeEnvHour: 'MihomeEnvHour', + Habit: 'Habit', + CheckIn: 'CheckIn', + CheckList: 'CheckList', + CheckListItem: 'CheckListItem', + CheckListTemplate: 'CheckListTemplate', + Project: 'Project', + ProjectMember: 'ProjectMember', + BoardColumn: 'BoardColumn', + Task: 'Task', + TaskMember: 'TaskMember', + TaskTag: 'TaskTag' } as const export type ModelName = (typeof ModelName)[keyof typeof ModelName] @@ -363,6 +374,181 @@ export const MihomeEnvHourScalarFieldEnum = { export type MihomeEnvHourScalarFieldEnum = (typeof MihomeEnvHourScalarFieldEnum)[keyof typeof MihomeEnvHourScalarFieldEnum] +export const HabitScalarFieldEnum = { + id: 'id', + userId: 'userId', + name: 'name', + period: 'period', + target: 'target', + color: 'color', + icon: 'icon', + note: 'note', + archived: 'archived', + sortOrder: 'sortOrder', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type HabitScalarFieldEnum = (typeof HabitScalarFieldEnum)[keyof typeof HabitScalarFieldEnum] + + +export const CheckInScalarFieldEnum = { + id: 'id', + habitId: 'habitId', + userId: 'userId', + date: 'date', + seq: 'seq', + note: 'note', + source: 'source', + createdAt: 'createdAt' +} as const + +export type CheckInScalarFieldEnum = (typeof CheckInScalarFieldEnum)[keyof typeof CheckInScalarFieldEnum] + + +export const CheckListScalarFieldEnum = { + id: 'id', + userId: 'userId', + title: 'title', + scene: 'scene', + note: 'note', + archived: 'archived', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type CheckListScalarFieldEnum = (typeof CheckListScalarFieldEnum)[keyof typeof CheckListScalarFieldEnum] + + +export const CheckListItemScalarFieldEnum = { + id: 'id', + listId: 'listId', + title: 'title', + note: 'note', + done: 'done', + doneAt: 'doneAt', + sortOrder: 'sortOrder', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type CheckListItemScalarFieldEnum = (typeof CheckListItemScalarFieldEnum)[keyof typeof CheckListItemScalarFieldEnum] + + +export const CheckListTemplateScalarFieldEnum = { + id: 'id', + userId: 'userId', + name: 'name', + scene: 'scene', + items: 'items', + builtin: 'builtin', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type CheckListTemplateScalarFieldEnum = (typeof CheckListTemplateScalarFieldEnum)[keyof typeof CheckListTemplateScalarFieldEnum] + + +export const ProjectScalarFieldEnum = { + id: 'id', + uuid: 'uuid', + code: 'code', + name: 'name', + ownerId: 'ownerId', + isPersonal: 'isPersonal', + archived: 'archived', + notes: 'notes', + taskKinds: 'taskKinds', + icon: 'icon', + autoArchive: 'autoArchive', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type ProjectScalarFieldEnum = (typeof ProjectScalarFieldEnum)[keyof typeof ProjectScalarFieldEnum] + + +export const ProjectMemberScalarFieldEnum = { + id: 'id', + uuid: 'uuid', + projectUuid: 'projectUuid', + projectId: 'projectId', + userId: 'userId', + permission: 'permission', + createdAt: 'createdAt' +} as const + +export type ProjectMemberScalarFieldEnum = (typeof ProjectMemberScalarFieldEnum)[keyof typeof ProjectMemberScalarFieldEnum] + + +export const BoardColumnScalarFieldEnum = { + id: 'id', + uuid: 'uuid', + projectUuid: 'projectUuid', + projectId: 'projectId', + name: 'name', + sortOrder: 'sortOrder', + boundStatus: 'boundStatus', + retention: 'retention', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type BoardColumnScalarFieldEnum = (typeof BoardColumnScalarFieldEnum)[keyof typeof BoardColumnScalarFieldEnum] + + +export const TaskScalarFieldEnum = { + id: 'id', + uuid: 'uuid', + projectUuid: 'projectUuid', + columnUuid: 'columnUuid', + parentUuid: 'parentUuid', + projectId: 'projectId', + columnId: 'columnId', + parentId: 'parentId', + title: 'title', + description: 'description', + kind: 'kind', + markDate: 'markDate', + status: 'status', + doneAt: 'doneAt', + assigneeId: 'assigneeId', + creatorId: 'creatorId', + extras: 'extras', + sortOrder: 'sortOrder', + archived: 'archived', + archivedAt: 'archivedAt', + archiveSource: 'archiveSource', + createdAt: 'createdAt', + updatedAt: 'updatedAt', + number: 'number' +} as const + +export type TaskScalarFieldEnum = (typeof TaskScalarFieldEnum)[keyof typeof TaskScalarFieldEnum] + + +export const TaskMemberScalarFieldEnum = { + id: 'id', + uuid: 'uuid', + taskUuid: 'taskUuid', + taskId: 'taskId', + userId: 'userId' +} as const + +export type TaskMemberScalarFieldEnum = (typeof TaskMemberScalarFieldEnum)[keyof typeof TaskMemberScalarFieldEnum] + + +export const TaskTagScalarFieldEnum = { + id: 'id', + uuid: 'uuid', + taskUuid: 'taskUuid', + taskId: 'taskId', + name: 'name' +} as const + +export type TaskTagScalarFieldEnum = (typeof TaskTagScalarFieldEnum)[keyof typeof TaskTagScalarFieldEnum] + + export const SortOrder = { asc: 'asc', desc: 'desc' @@ -386,23 +572,12 @@ export const NullableJsonNullValueInput = { export type NullableJsonNullValueInput = (typeof NullableJsonNullValueInput)[keyof typeof NullableJsonNullValueInput] -export const UserOrderByRelevanceFieldEnum = { - name: 'name', - email: 'email', - password: 'password' +export const QueryMode = { + default: 'default', + insensitive: 'insensitive' } as const -export type UserOrderByRelevanceFieldEnum = (typeof UserOrderByRelevanceFieldEnum)[keyof typeof UserOrderByRelevanceFieldEnum] - - -export const ArticleOrderByRelevanceFieldEnum = { - title: 'title', - slug: 'slug', - content: 'content', - date: 'date' -} as const - -export type ArticleOrderByRelevanceFieldEnum = (typeof ArticleOrderByRelevanceFieldEnum)[keyof typeof ArticleOrderByRelevanceFieldEnum] +export type QueryMode = (typeof QueryMode)[keyof typeof QueryMode] export const JsonNullValueFilter = { @@ -414,41 +589,6 @@ export const JsonNullValueFilter = { export type JsonNullValueFilter = (typeof JsonNullValueFilter)[keyof typeof JsonNullValueFilter] -export const QueryMode = { - default: 'default', - insensitive: 'insensitive' -} as const - -export type QueryMode = (typeof QueryMode)[keyof typeof QueryMode] - - -export const StrategyOrderByRelevanceFieldEnum = { - name: 'name', - description: 'description' -} as const - -export type StrategyOrderByRelevanceFieldEnum = (typeof StrategyOrderByRelevanceFieldEnum)[keyof typeof StrategyOrderByRelevanceFieldEnum] - - -export const IndicatorOrderByRelevanceFieldEnum = { - name: 'name', - groupName: 'groupName', - title: 'title', - unit: 'unit', - source: 'source', - frequency: 'frequency' -} as const - -export type IndicatorOrderByRelevanceFieldEnum = (typeof IndicatorOrderByRelevanceFieldEnum)[keyof typeof IndicatorOrderByRelevanceFieldEnum] - - -export const IndicatorPointOrderByRelevanceFieldEnum = { - period: 'period' -} as const - -export type IndicatorPointOrderByRelevanceFieldEnum = (typeof IndicatorPointOrderByRelevanceFieldEnum)[keyof typeof IndicatorPointOrderByRelevanceFieldEnum] - - export const NullsOrder = { first: 'first', last: 'last' @@ -456,113 +596,3 @@ export const NullsOrder = { export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder] - -export const NewsOrderByRelevanceFieldEnum = { - title: 'title', - summary: 'summary', - content: 'content', - source: 'source', - url: 'url' -} as const - -export type NewsOrderByRelevanceFieldEnum = (typeof NewsOrderByRelevanceFieldEnum)[keyof typeof NewsOrderByRelevanceFieldEnum] - - -export const AlertOrderByRelevanceFieldEnum = { - indicatorName: 'indicatorName', - period: 'period', - sourceA: 'sourceA', - sourceB: 'sourceB' -} as const - -export type AlertOrderByRelevanceFieldEnum = (typeof AlertOrderByRelevanceFieldEnum)[keyof typeof AlertOrderByRelevanceFieldEnum] - - -export const ServerNodeOrderByRelevanceFieldEnum = { - uuid: 'uuid', - name: 'name', - cpuName: 'cpuName', - os: 'os', - region: 'region', - status: 'status' -} as const - -export type ServerNodeOrderByRelevanceFieldEnum = (typeof ServerNodeOrderByRelevanceFieldEnum)[keyof typeof ServerNodeOrderByRelevanceFieldEnum] - - -export const ServerNodeHistoryOrderByRelevanceFieldEnum = { - uuid: 'uuid' -} as const - -export type ServerNodeHistoryOrderByRelevanceFieldEnum = (typeof ServerNodeHistoryOrderByRelevanceFieldEnum)[keyof typeof ServerNodeHistoryOrderByRelevanceFieldEnum] - - -export const ServiceCheckOrderByRelevanceFieldEnum = { - name: 'name', - url: 'url', - status: 'status', - message: 'message' -} as const - -export type ServiceCheckOrderByRelevanceFieldEnum = (typeof ServiceCheckOrderByRelevanceFieldEnum)[keyof typeof ServiceCheckOrderByRelevanceFieldEnum] - - -export const Econ_SowInventoryOrderByRelevanceFieldEnum = { - month: 'month' -} as const - -export type Econ_SowInventoryOrderByRelevanceFieldEnum = (typeof Econ_SowInventoryOrderByRelevanceFieldEnum)[keyof typeof Econ_SowInventoryOrderByRelevanceFieldEnum] - - -export const ScraperCacheOrderByRelevanceFieldEnum = { - scraperName: 'scraperName', - lastDataHash: 'lastDataHash' -} as const - -export type ScraperCacheOrderByRelevanceFieldEnum = (typeof ScraperCacheOrderByRelevanceFieldEnum)[keyof typeof ScraperCacheOrderByRelevanceFieldEnum] - - -export const OilPriceHistoryOrderByRelevanceFieldEnum = { - province: 'province', - fuel: 'fuel', - source: 'source' -} as const - -export type OilPriceHistoryOrderByRelevanceFieldEnum = (typeof OilPriceHistoryOrderByRelevanceFieldEnum)[keyof typeof OilPriceHistoryOrderByRelevanceFieldEnum] - - -export const MihomeAccountOrderByRelevanceFieldEnum = { - key: 'key', - userId: 'userId', - status: 'status', - lastError: 'lastError' -} as const - -export type MihomeAccountOrderByRelevanceFieldEnum = (typeof MihomeAccountOrderByRelevanceFieldEnum)[keyof typeof MihomeAccountOrderByRelevanceFieldEnum] - - -export const MihomeHomeOrderByRelevanceFieldEnum = { - homeId: 'homeId', - name: 'name' -} as const - -export type MihomeHomeOrderByRelevanceFieldEnum = (typeof MihomeHomeOrderByRelevanceFieldEnum)[keyof typeof MihomeHomeOrderByRelevanceFieldEnum] - - -export const MihomeDeviceOrderByRelevanceFieldEnum = { - did: 'did', - homeId: 'homeId', - model: 'model', - name: 'name', - alias: 'alias' -} as const - -export type MihomeDeviceOrderByRelevanceFieldEnum = (typeof MihomeDeviceOrderByRelevanceFieldEnum)[keyof typeof MihomeDeviceOrderByRelevanceFieldEnum] - - -export const MihomeEnvSampleOrderByRelevanceFieldEnum = { - origin: 'origin' -} as const - -export type MihomeEnvSampleOrderByRelevanceFieldEnum = (typeof MihomeEnvSampleOrderByRelevanceFieldEnum)[keyof typeof MihomeEnvSampleOrderByRelevanceFieldEnum] - diff --git a/generated/prisma/models.ts b/generated/prisma/models.ts index 05ae935..cd74c6b 100644 --- a/generated/prisma/models.ts +++ b/generated/prisma/models.ts @@ -26,4 +26,15 @@ export type * from './models/MihomeHome.ts' export type * from './models/MihomeDevice.ts' export type * from './models/MihomeEnvSample.ts' export type * from './models/MihomeEnvHour.ts' +export type * from './models/Habit.ts' +export type * from './models/CheckIn.ts' +export type * from './models/CheckList.ts' +export type * from './models/CheckListItem.ts' +export type * from './models/CheckListTemplate.ts' +export type * from './models/Project.ts' +export type * from './models/ProjectMember.ts' +export type * from './models/BoardColumn.ts' +export type * from './models/Task.ts' +export type * from './models/TaskMember.ts' +export type * from './models/TaskTag.ts' export type * from './commonInputTypes.ts' \ No newline at end of file diff --git a/generated/prisma/models/Alert.ts b/generated/prisma/models/Alert.ts index 2d2db2a..1d44094 100644 --- a/generated/prisma/models/Alert.ts +++ b/generated/prisma/models/Alert.ts @@ -285,7 +285,6 @@ export type AlertOrderByWithRelationInput = { diff?: Prisma.SortOrder diffRatio?: Prisma.SortOrder createdAt?: Prisma.SortOrder - _relevance?: Prisma.AlertOrderByRelevanceInput } export type AlertWhereUniqueInput = Prisma.AtLeast<{ @@ -426,12 +425,6 @@ export type AlertUncheckedUpdateManyInput = { createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string } -export type AlertOrderByRelevanceInput = { - fields: Prisma.AlertOrderByRelevanceFieldEnum | Prisma.AlertOrderByRelevanceFieldEnum[] - sort: Prisma.SortOrder - search: string -} - export type AlertCountOrderByAggregateInput = { id?: Prisma.SortOrder indicatorName?: Prisma.SortOrder @@ -502,7 +495,31 @@ export type AlertSelect +export type AlertSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + indicatorName?: boolean + period?: boolean + valueA?: boolean + valueB?: boolean + sourceA?: boolean + sourceB?: boolean + diff?: boolean + diffRatio?: boolean + createdAt?: boolean +}, ExtArgs["result"]["alert"]> +export type AlertSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + indicatorName?: boolean + period?: boolean + valueA?: boolean + valueB?: boolean + sourceA?: boolean + sourceB?: boolean + diff?: boolean + diffRatio?: boolean + createdAt?: boolean +}, ExtArgs["result"]["alert"]> export type AlertSelectScalar = { id?: boolean @@ -650,6 +667,30 @@ export interface AlertDelegate(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Create many Alerts and returns the data saved in the database. + * @param {AlertCreateManyAndReturnArgs} args - Arguments to create many Alerts. + * @example + * // Create many Alerts + * const alert = await prisma.alert.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many Alerts and only return the `id` + * const alertWithIdOnly = await prisma.alert.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + /** * Delete a Alert. * @param {AlertDeleteArgs} args - Arguments to delete one Alert. @@ -714,6 +755,36 @@ export interface AlertDelegate(args: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Update zero or more Alerts and returns the data updated in the database. + * @param {AlertUpdateManyAndReturnArgs} args - Arguments to update many Alerts. + * @example + * // Update many Alerts + * const alert = await prisma.alert.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more Alerts and only return the `id` + * const alertWithIdOnly = await prisma.alert.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + /** * Create or update one Alert. * @param {AlertUpsertArgs} args - Arguments to update or create a Alert. @@ -1125,6 +1196,25 @@ export type AlertCreateManyArgs = { + /** + * Select specific fields to fetch from the Alert + */ + select?: Prisma.AlertSelectCreateManyAndReturn | null + /** + * Omit specific fields from the Alert + */ + omit?: Prisma.AlertOmit | null + /** + * The data used to create many Alerts. + */ + data: Prisma.AlertCreateManyInput | Prisma.AlertCreateManyInput[] + skipDuplicates?: boolean +} + /** * Alert update */ @@ -1165,6 +1255,32 @@ export type AlertUpdateManyArgs = { + /** + * Select specific fields to fetch from the Alert + */ + select?: Prisma.AlertSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the Alert + */ + omit?: Prisma.AlertOmit | null + /** + * The data used to update Alerts. + */ + data: Prisma.XOR + /** + * Filter which Alerts to update + */ + where?: Prisma.AlertWhereInput + /** + * Limit how many Alerts to update. + */ + limit?: number +} + /** * Alert upsert */ diff --git a/generated/prisma/models/Article.ts b/generated/prisma/models/Article.ts index 4a9bf6d..cc533ca 100644 --- a/generated/prisma/models/Article.ts +++ b/generated/prisma/models/Article.ts @@ -242,7 +242,6 @@ export type ArticleOrderByWithRelationInput = { date?: Prisma.SortOrder createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder - _relevance?: Prisma.ArticleOrderByRelevanceInput } export type ArticleWhereUniqueInput = Prisma.AtLeast<{ @@ -353,12 +352,6 @@ export type ArticleUncheckedUpdateManyInput = { updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string } -export type ArticleOrderByRelevanceInput = { - fields: Prisma.ArticleOrderByRelevanceFieldEnum | Prisma.ArticleOrderByRelevanceFieldEnum[] - sort: Prisma.SortOrder - search: string -} - export type ArticleCountOrderByAggregateInput = { id?: Prisma.SortOrder title?: Prisma.SortOrder @@ -409,7 +402,25 @@ export type ArticleSelect +export type ArticleSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + title?: boolean + slug?: boolean + content?: boolean + date?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["article"]> +export type ArticleSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + title?: boolean + slug?: boolean + content?: boolean + date?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["article"]> export type ArticleSelectScalar = { id?: boolean @@ -551,6 +562,30 @@ export interface ArticleDelegate(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Create many Articles and returns the data saved in the database. + * @param {ArticleCreateManyAndReturnArgs} args - Arguments to create many Articles. + * @example + * // Create many Articles + * const article = await prisma.article.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many Articles and only return the `id` + * const articleWithIdOnly = await prisma.article.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + /** * Delete a Article. * @param {ArticleDeleteArgs} args - Arguments to delete one Article. @@ -615,6 +650,36 @@ export interface ArticleDelegate(args: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Update zero or more Articles and returns the data updated in the database. + * @param {ArticleUpdateManyAndReturnArgs} args - Arguments to update many Articles. + * @example + * // Update many Articles + * const article = await prisma.article.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more Articles and only return the `id` + * const articleWithIdOnly = await prisma.article.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + /** * Create or update one Article. * @param {ArticleUpsertArgs} args - Arguments to update or create a Article. @@ -1023,6 +1088,25 @@ export type ArticleCreateManyArgs = { + /** + * Select specific fields to fetch from the Article + */ + select?: Prisma.ArticleSelectCreateManyAndReturn | null + /** + * Omit specific fields from the Article + */ + omit?: Prisma.ArticleOmit | null + /** + * The data used to create many Articles. + */ + data: Prisma.ArticleCreateManyInput | Prisma.ArticleCreateManyInput[] + skipDuplicates?: boolean +} + /** * Article update */ @@ -1063,6 +1147,32 @@ export type ArticleUpdateManyArgs = { + /** + * Select specific fields to fetch from the Article + */ + select?: Prisma.ArticleSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the Article + */ + omit?: Prisma.ArticleOmit | null + /** + * The data used to update Articles. + */ + data: Prisma.XOR + /** + * Filter which Articles to update + */ + where?: Prisma.ArticleWhereInput + /** + * Limit how many Articles to update. + */ + limit?: number +} + /** * Article upsert */ diff --git a/generated/prisma/models/BoardColumn.ts b/generated/prisma/models/BoardColumn.ts new file mode 100644 index 0000000..42984ba --- /dev/null +++ b/generated/prisma/models/BoardColumn.ts @@ -0,0 +1,1732 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `BoardColumn` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums.ts" +import type * as Prisma from "../internal/prismaNamespace.ts" + +/** + * Model BoardColumn + * + */ +export type BoardColumnModel = runtime.Types.Result.DefaultSelection + +export type AggregateBoardColumn = { + _count: BoardColumnCountAggregateOutputType | null + _avg: BoardColumnAvgAggregateOutputType | null + _sum: BoardColumnSumAggregateOutputType | null + _min: BoardColumnMinAggregateOutputType | null + _max: BoardColumnMaxAggregateOutputType | null +} + +export type BoardColumnAvgAggregateOutputType = { + id: number | null + projectId: number | null + sortOrder: number | null +} + +export type BoardColumnSumAggregateOutputType = { + id: number | null + projectId: number | null + sortOrder: number | null +} + +export type BoardColumnMinAggregateOutputType = { + id: number | null + uuid: string | null + projectUuid: string | null + projectId: number | null + name: string | null + sortOrder: number | null + boundStatus: string | null + retention: string | null + createdAt: Date | null + updatedAt: Date | null +} + +export type BoardColumnMaxAggregateOutputType = { + id: number | null + uuid: string | null + projectUuid: string | null + projectId: number | null + name: string | null + sortOrder: number | null + boundStatus: string | null + retention: string | null + createdAt: Date | null + updatedAt: Date | null +} + +export type BoardColumnCountAggregateOutputType = { + id: number + uuid: number + projectUuid: number + projectId: number + name: number + sortOrder: number + boundStatus: number + retention: number + createdAt: number + updatedAt: number + _all: number +} + + +export type BoardColumnAvgAggregateInputType = { + id?: true + projectId?: true + sortOrder?: true +} + +export type BoardColumnSumAggregateInputType = { + id?: true + projectId?: true + sortOrder?: true +} + +export type BoardColumnMinAggregateInputType = { + id?: true + uuid?: true + projectUuid?: true + projectId?: true + name?: true + sortOrder?: true + boundStatus?: true + retention?: true + createdAt?: true + updatedAt?: true +} + +export type BoardColumnMaxAggregateInputType = { + id?: true + uuid?: true + projectUuid?: true + projectId?: true + name?: true + sortOrder?: true + boundStatus?: true + retention?: true + createdAt?: true + updatedAt?: true +} + +export type BoardColumnCountAggregateInputType = { + id?: true + uuid?: true + projectUuid?: true + projectId?: true + name?: true + sortOrder?: true + boundStatus?: true + retention?: true + createdAt?: true + updatedAt?: true + _all?: true +} + +export type BoardColumnAggregateArgs = { + /** + * Filter which BoardColumn to aggregate. + */ + where?: Prisma.BoardColumnWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of BoardColumns to fetch. + */ + orderBy?: Prisma.BoardColumnOrderByWithRelationInput | Prisma.BoardColumnOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.BoardColumnWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` BoardColumns from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` BoardColumns. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned BoardColumns + **/ + _count?: true | BoardColumnCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to average + **/ + _avg?: BoardColumnAvgAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to sum + **/ + _sum?: BoardColumnSumAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: BoardColumnMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: BoardColumnMaxAggregateInputType +} + +export type GetBoardColumnAggregateType = { + [P in keyof T & keyof AggregateBoardColumn]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type BoardColumnGroupByArgs = { + where?: Prisma.BoardColumnWhereInput + orderBy?: Prisma.BoardColumnOrderByWithAggregationInput | Prisma.BoardColumnOrderByWithAggregationInput[] + by: Prisma.BoardColumnScalarFieldEnum[] | Prisma.BoardColumnScalarFieldEnum + having?: Prisma.BoardColumnScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: BoardColumnCountAggregateInputType | true + _avg?: BoardColumnAvgAggregateInputType + _sum?: BoardColumnSumAggregateInputType + _min?: BoardColumnMinAggregateInputType + _max?: BoardColumnMaxAggregateInputType +} + +export type BoardColumnGroupByOutputType = { + id: number + uuid: string + projectUuid: string | null + projectId: number + name: string + sortOrder: number + boundStatus: string | null + retention: string + createdAt: Date + updatedAt: Date + _count: BoardColumnCountAggregateOutputType | null + _avg: BoardColumnAvgAggregateOutputType | null + _sum: BoardColumnSumAggregateOutputType | null + _min: BoardColumnMinAggregateOutputType | null + _max: BoardColumnMaxAggregateOutputType | null +} + +export type GetBoardColumnGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof BoardColumnGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type BoardColumnWhereInput = { + AND?: Prisma.BoardColumnWhereInput | Prisma.BoardColumnWhereInput[] + OR?: Prisma.BoardColumnWhereInput[] + NOT?: Prisma.BoardColumnWhereInput | Prisma.BoardColumnWhereInput[] + id?: Prisma.IntFilter<"BoardColumn"> | number + uuid?: Prisma.StringFilter<"BoardColumn"> | string + projectUuid?: Prisma.StringNullableFilter<"BoardColumn"> | string | null + projectId?: Prisma.IntFilter<"BoardColumn"> | number + name?: Prisma.StringFilter<"BoardColumn"> | string + sortOrder?: Prisma.IntFilter<"BoardColumn"> | number + boundStatus?: Prisma.StringNullableFilter<"BoardColumn"> | string | null + retention?: Prisma.StringFilter<"BoardColumn"> | string + createdAt?: Prisma.DateTimeFilter<"BoardColumn"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"BoardColumn"> | Date | string + project?: Prisma.XOR + tasks?: Prisma.TaskListRelationFilter +} + +export type BoardColumnOrderByWithRelationInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + projectUuid?: Prisma.SortOrderInput | Prisma.SortOrder + projectId?: Prisma.SortOrder + name?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + boundStatus?: Prisma.SortOrderInput | Prisma.SortOrder + retention?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + project?: Prisma.ProjectOrderByWithRelationInput + tasks?: Prisma.TaskOrderByRelationAggregateInput +} + +export type BoardColumnWhereUniqueInput = Prisma.AtLeast<{ + id?: number + uuid?: string + projectId_name?: Prisma.BoardColumnProjectIdNameCompoundUniqueInput + AND?: Prisma.BoardColumnWhereInput | Prisma.BoardColumnWhereInput[] + OR?: Prisma.BoardColumnWhereInput[] + NOT?: Prisma.BoardColumnWhereInput | Prisma.BoardColumnWhereInput[] + projectUuid?: Prisma.StringNullableFilter<"BoardColumn"> | string | null + projectId?: Prisma.IntFilter<"BoardColumn"> | number + name?: Prisma.StringFilter<"BoardColumn"> | string + sortOrder?: Prisma.IntFilter<"BoardColumn"> | number + boundStatus?: Prisma.StringNullableFilter<"BoardColumn"> | string | null + retention?: Prisma.StringFilter<"BoardColumn"> | string + createdAt?: Prisma.DateTimeFilter<"BoardColumn"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"BoardColumn"> | Date | string + project?: Prisma.XOR + tasks?: Prisma.TaskListRelationFilter +}, "id" | "uuid" | "projectId_name"> + +export type BoardColumnOrderByWithAggregationInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + projectUuid?: Prisma.SortOrderInput | Prisma.SortOrder + projectId?: Prisma.SortOrder + name?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + boundStatus?: Prisma.SortOrderInput | Prisma.SortOrder + retention?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + _count?: Prisma.BoardColumnCountOrderByAggregateInput + _avg?: Prisma.BoardColumnAvgOrderByAggregateInput + _max?: Prisma.BoardColumnMaxOrderByAggregateInput + _min?: Prisma.BoardColumnMinOrderByAggregateInput + _sum?: Prisma.BoardColumnSumOrderByAggregateInput +} + +export type BoardColumnScalarWhereWithAggregatesInput = { + AND?: Prisma.BoardColumnScalarWhereWithAggregatesInput | Prisma.BoardColumnScalarWhereWithAggregatesInput[] + OR?: Prisma.BoardColumnScalarWhereWithAggregatesInput[] + NOT?: Prisma.BoardColumnScalarWhereWithAggregatesInput | Prisma.BoardColumnScalarWhereWithAggregatesInput[] + id?: Prisma.IntWithAggregatesFilter<"BoardColumn"> | number + uuid?: Prisma.StringWithAggregatesFilter<"BoardColumn"> | string + projectUuid?: Prisma.StringNullableWithAggregatesFilter<"BoardColumn"> | string | null + projectId?: Prisma.IntWithAggregatesFilter<"BoardColumn"> | number + name?: Prisma.StringWithAggregatesFilter<"BoardColumn"> | string + sortOrder?: Prisma.IntWithAggregatesFilter<"BoardColumn"> | number + boundStatus?: Prisma.StringNullableWithAggregatesFilter<"BoardColumn"> | string | null + retention?: Prisma.StringWithAggregatesFilter<"BoardColumn"> | string + createdAt?: Prisma.DateTimeWithAggregatesFilter<"BoardColumn"> | Date | string + updatedAt?: Prisma.DateTimeWithAggregatesFilter<"BoardColumn"> | Date | string +} + +export type BoardColumnCreateInput = { + uuid: string + projectUuid?: string | null + name: string + sortOrder?: number + boundStatus?: string | null + retention?: string + createdAt?: Date | string + updatedAt?: Date | string + project: Prisma.ProjectCreateNestedOneWithoutColumnsInput + tasks?: Prisma.TaskCreateNestedManyWithoutColumnInput +} + +export type BoardColumnUncheckedCreateInput = { + id?: number + uuid: string + projectUuid?: string | null + projectId: number + name: string + sortOrder?: number + boundStatus?: string | null + retention?: string + createdAt?: Date | string + updatedAt?: Date | string + tasks?: Prisma.TaskUncheckedCreateNestedManyWithoutColumnInput +} + +export type BoardColumnUpdateInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + name?: Prisma.StringFieldUpdateOperationsInput | string + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + boundStatus?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + retention?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + project?: Prisma.ProjectUpdateOneRequiredWithoutColumnsNestedInput + tasks?: Prisma.TaskUpdateManyWithoutColumnNestedInput +} + +export type BoardColumnUncheckedUpdateInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + boundStatus?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + retention?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + tasks?: Prisma.TaskUncheckedUpdateManyWithoutColumnNestedInput +} + +export type BoardColumnCreateManyInput = { + id?: number + uuid: string + projectUuid?: string | null + projectId: number + name: string + sortOrder?: number + boundStatus?: string | null + retention?: string + createdAt?: Date | string + updatedAt?: Date | string +} + +export type BoardColumnUpdateManyMutationInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + name?: Prisma.StringFieldUpdateOperationsInput | string + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + boundStatus?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + retention?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type BoardColumnUncheckedUpdateManyInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + boundStatus?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + retention?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type BoardColumnListRelationFilter = { + every?: Prisma.BoardColumnWhereInput + some?: Prisma.BoardColumnWhereInput + none?: Prisma.BoardColumnWhereInput +} + +export type BoardColumnOrderByRelationAggregateInput = { + _count?: Prisma.SortOrder +} + +export type BoardColumnProjectIdNameCompoundUniqueInput = { + projectId: number + name: string +} + +export type BoardColumnCountOrderByAggregateInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + projectUuid?: Prisma.SortOrder + projectId?: Prisma.SortOrder + name?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + boundStatus?: Prisma.SortOrder + retention?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type BoardColumnAvgOrderByAggregateInput = { + id?: Prisma.SortOrder + projectId?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder +} + +export type BoardColumnMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + projectUuid?: Prisma.SortOrder + projectId?: Prisma.SortOrder + name?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + boundStatus?: Prisma.SortOrder + retention?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type BoardColumnMinOrderByAggregateInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + projectUuid?: Prisma.SortOrder + projectId?: Prisma.SortOrder + name?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + boundStatus?: Prisma.SortOrder + retention?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type BoardColumnSumOrderByAggregateInput = { + id?: Prisma.SortOrder + projectId?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder +} + +export type BoardColumnScalarRelationFilter = { + is?: Prisma.BoardColumnWhereInput + isNot?: Prisma.BoardColumnWhereInput +} + +export type BoardColumnCreateNestedManyWithoutProjectInput = { + create?: Prisma.XOR | Prisma.BoardColumnCreateWithoutProjectInput[] | Prisma.BoardColumnUncheckedCreateWithoutProjectInput[] + connectOrCreate?: Prisma.BoardColumnCreateOrConnectWithoutProjectInput | Prisma.BoardColumnCreateOrConnectWithoutProjectInput[] + createMany?: Prisma.BoardColumnCreateManyProjectInputEnvelope + connect?: Prisma.BoardColumnWhereUniqueInput | Prisma.BoardColumnWhereUniqueInput[] +} + +export type BoardColumnUncheckedCreateNestedManyWithoutProjectInput = { + create?: Prisma.XOR | Prisma.BoardColumnCreateWithoutProjectInput[] | Prisma.BoardColumnUncheckedCreateWithoutProjectInput[] + connectOrCreate?: Prisma.BoardColumnCreateOrConnectWithoutProjectInput | Prisma.BoardColumnCreateOrConnectWithoutProjectInput[] + createMany?: Prisma.BoardColumnCreateManyProjectInputEnvelope + connect?: Prisma.BoardColumnWhereUniqueInput | Prisma.BoardColumnWhereUniqueInput[] +} + +export type BoardColumnUpdateManyWithoutProjectNestedInput = { + create?: Prisma.XOR | Prisma.BoardColumnCreateWithoutProjectInput[] | Prisma.BoardColumnUncheckedCreateWithoutProjectInput[] + connectOrCreate?: Prisma.BoardColumnCreateOrConnectWithoutProjectInput | Prisma.BoardColumnCreateOrConnectWithoutProjectInput[] + upsert?: Prisma.BoardColumnUpsertWithWhereUniqueWithoutProjectInput | Prisma.BoardColumnUpsertWithWhereUniqueWithoutProjectInput[] + createMany?: Prisma.BoardColumnCreateManyProjectInputEnvelope + set?: Prisma.BoardColumnWhereUniqueInput | Prisma.BoardColumnWhereUniqueInput[] + disconnect?: Prisma.BoardColumnWhereUniqueInput | Prisma.BoardColumnWhereUniqueInput[] + delete?: Prisma.BoardColumnWhereUniqueInput | Prisma.BoardColumnWhereUniqueInput[] + connect?: Prisma.BoardColumnWhereUniqueInput | Prisma.BoardColumnWhereUniqueInput[] + update?: Prisma.BoardColumnUpdateWithWhereUniqueWithoutProjectInput | Prisma.BoardColumnUpdateWithWhereUniqueWithoutProjectInput[] + updateMany?: Prisma.BoardColumnUpdateManyWithWhereWithoutProjectInput | Prisma.BoardColumnUpdateManyWithWhereWithoutProjectInput[] + deleteMany?: Prisma.BoardColumnScalarWhereInput | Prisma.BoardColumnScalarWhereInput[] +} + +export type BoardColumnUncheckedUpdateManyWithoutProjectNestedInput = { + create?: Prisma.XOR | Prisma.BoardColumnCreateWithoutProjectInput[] | Prisma.BoardColumnUncheckedCreateWithoutProjectInput[] + connectOrCreate?: Prisma.BoardColumnCreateOrConnectWithoutProjectInput | Prisma.BoardColumnCreateOrConnectWithoutProjectInput[] + upsert?: Prisma.BoardColumnUpsertWithWhereUniqueWithoutProjectInput | Prisma.BoardColumnUpsertWithWhereUniqueWithoutProjectInput[] + createMany?: Prisma.BoardColumnCreateManyProjectInputEnvelope + set?: Prisma.BoardColumnWhereUniqueInput | Prisma.BoardColumnWhereUniqueInput[] + disconnect?: Prisma.BoardColumnWhereUniqueInput | Prisma.BoardColumnWhereUniqueInput[] + delete?: Prisma.BoardColumnWhereUniqueInput | Prisma.BoardColumnWhereUniqueInput[] + connect?: Prisma.BoardColumnWhereUniqueInput | Prisma.BoardColumnWhereUniqueInput[] + update?: Prisma.BoardColumnUpdateWithWhereUniqueWithoutProjectInput | Prisma.BoardColumnUpdateWithWhereUniqueWithoutProjectInput[] + updateMany?: Prisma.BoardColumnUpdateManyWithWhereWithoutProjectInput | Prisma.BoardColumnUpdateManyWithWhereWithoutProjectInput[] + deleteMany?: Prisma.BoardColumnScalarWhereInput | Prisma.BoardColumnScalarWhereInput[] +} + +export type BoardColumnCreateNestedOneWithoutTasksInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.BoardColumnCreateOrConnectWithoutTasksInput + connect?: Prisma.BoardColumnWhereUniqueInput +} + +export type BoardColumnUpdateOneRequiredWithoutTasksNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.BoardColumnCreateOrConnectWithoutTasksInput + upsert?: Prisma.BoardColumnUpsertWithoutTasksInput + connect?: Prisma.BoardColumnWhereUniqueInput + update?: Prisma.XOR, Prisma.BoardColumnUncheckedUpdateWithoutTasksInput> +} + +export type BoardColumnCreateWithoutProjectInput = { + uuid: string + projectUuid?: string | null + name: string + sortOrder?: number + boundStatus?: string | null + retention?: string + createdAt?: Date | string + updatedAt?: Date | string + tasks?: Prisma.TaskCreateNestedManyWithoutColumnInput +} + +export type BoardColumnUncheckedCreateWithoutProjectInput = { + id?: number + uuid: string + projectUuid?: string | null + name: string + sortOrder?: number + boundStatus?: string | null + retention?: string + createdAt?: Date | string + updatedAt?: Date | string + tasks?: Prisma.TaskUncheckedCreateNestedManyWithoutColumnInput +} + +export type BoardColumnCreateOrConnectWithoutProjectInput = { + where: Prisma.BoardColumnWhereUniqueInput + create: Prisma.XOR +} + +export type BoardColumnCreateManyProjectInputEnvelope = { + data: Prisma.BoardColumnCreateManyProjectInput | Prisma.BoardColumnCreateManyProjectInput[] + skipDuplicates?: boolean +} + +export type BoardColumnUpsertWithWhereUniqueWithoutProjectInput = { + where: Prisma.BoardColumnWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type BoardColumnUpdateWithWhereUniqueWithoutProjectInput = { + where: Prisma.BoardColumnWhereUniqueInput + data: Prisma.XOR +} + +export type BoardColumnUpdateManyWithWhereWithoutProjectInput = { + where: Prisma.BoardColumnScalarWhereInput + data: Prisma.XOR +} + +export type BoardColumnScalarWhereInput = { + AND?: Prisma.BoardColumnScalarWhereInput | Prisma.BoardColumnScalarWhereInput[] + OR?: Prisma.BoardColumnScalarWhereInput[] + NOT?: Prisma.BoardColumnScalarWhereInput | Prisma.BoardColumnScalarWhereInput[] + id?: Prisma.IntFilter<"BoardColumn"> | number + uuid?: Prisma.StringFilter<"BoardColumn"> | string + projectUuid?: Prisma.StringNullableFilter<"BoardColumn"> | string | null + projectId?: Prisma.IntFilter<"BoardColumn"> | number + name?: Prisma.StringFilter<"BoardColumn"> | string + sortOrder?: Prisma.IntFilter<"BoardColumn"> | number + boundStatus?: Prisma.StringNullableFilter<"BoardColumn"> | string | null + retention?: Prisma.StringFilter<"BoardColumn"> | string + createdAt?: Prisma.DateTimeFilter<"BoardColumn"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"BoardColumn"> | Date | string +} + +export type BoardColumnCreateWithoutTasksInput = { + uuid: string + projectUuid?: string | null + name: string + sortOrder?: number + boundStatus?: string | null + retention?: string + createdAt?: Date | string + updatedAt?: Date | string + project: Prisma.ProjectCreateNestedOneWithoutColumnsInput +} + +export type BoardColumnUncheckedCreateWithoutTasksInput = { + id?: number + uuid: string + projectUuid?: string | null + projectId: number + name: string + sortOrder?: number + boundStatus?: string | null + retention?: string + createdAt?: Date | string + updatedAt?: Date | string +} + +export type BoardColumnCreateOrConnectWithoutTasksInput = { + where: Prisma.BoardColumnWhereUniqueInput + create: Prisma.XOR +} + +export type BoardColumnUpsertWithoutTasksInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.BoardColumnWhereInput +} + +export type BoardColumnUpdateToOneWithWhereWithoutTasksInput = { + where?: Prisma.BoardColumnWhereInput + data: Prisma.XOR +} + +export type BoardColumnUpdateWithoutTasksInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + name?: Prisma.StringFieldUpdateOperationsInput | string + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + boundStatus?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + retention?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + project?: Prisma.ProjectUpdateOneRequiredWithoutColumnsNestedInput +} + +export type BoardColumnUncheckedUpdateWithoutTasksInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + boundStatus?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + retention?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type BoardColumnCreateManyProjectInput = { + id?: number + uuid: string + projectUuid?: string | null + name: string + sortOrder?: number + boundStatus?: string | null + retention?: string + createdAt?: Date | string + updatedAt?: Date | string +} + +export type BoardColumnUpdateWithoutProjectInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + name?: Prisma.StringFieldUpdateOperationsInput | string + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + boundStatus?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + retention?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + tasks?: Prisma.TaskUpdateManyWithoutColumnNestedInput +} + +export type BoardColumnUncheckedUpdateWithoutProjectInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + name?: Prisma.StringFieldUpdateOperationsInput | string + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + boundStatus?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + retention?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + tasks?: Prisma.TaskUncheckedUpdateManyWithoutColumnNestedInput +} + +export type BoardColumnUncheckedUpdateManyWithoutProjectInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + name?: Prisma.StringFieldUpdateOperationsInput | string + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + boundStatus?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + retention?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + + +/** + * Count Type BoardColumnCountOutputType + */ + +export type BoardColumnCountOutputType = { + tasks: number +} + +export type BoardColumnCountOutputTypeSelect = { + tasks?: boolean | BoardColumnCountOutputTypeCountTasksArgs +} + +/** + * BoardColumnCountOutputType without action + */ +export type BoardColumnCountOutputTypeDefaultArgs = { + /** + * Select specific fields to fetch from the BoardColumnCountOutputType + */ + select?: Prisma.BoardColumnCountOutputTypeSelect | null +} + +/** + * BoardColumnCountOutputType without action + */ +export type BoardColumnCountOutputTypeCountTasksArgs = { + where?: Prisma.TaskWhereInput +} + + +export type BoardColumnSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + projectUuid?: boolean + projectId?: boolean + name?: boolean + sortOrder?: boolean + boundStatus?: boolean + retention?: boolean + createdAt?: boolean + updatedAt?: boolean + project?: boolean | Prisma.ProjectDefaultArgs + tasks?: boolean | Prisma.BoardColumn$tasksArgs + _count?: boolean | Prisma.BoardColumnCountOutputTypeDefaultArgs +}, ExtArgs["result"]["boardColumn"]> + +export type BoardColumnSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + projectUuid?: boolean + projectId?: boolean + name?: boolean + sortOrder?: boolean + boundStatus?: boolean + retention?: boolean + createdAt?: boolean + updatedAt?: boolean + project?: boolean | Prisma.ProjectDefaultArgs +}, ExtArgs["result"]["boardColumn"]> + +export type BoardColumnSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + projectUuid?: boolean + projectId?: boolean + name?: boolean + sortOrder?: boolean + boundStatus?: boolean + retention?: boolean + createdAt?: boolean + updatedAt?: boolean + project?: boolean | Prisma.ProjectDefaultArgs +}, ExtArgs["result"]["boardColumn"]> + +export type BoardColumnSelectScalar = { + id?: boolean + uuid?: boolean + projectUuid?: boolean + projectId?: boolean + name?: boolean + sortOrder?: boolean + boundStatus?: boolean + retention?: boolean + createdAt?: boolean + updatedAt?: boolean +} + +export type BoardColumnOmit = runtime.Types.Extensions.GetOmit<"id" | "uuid" | "projectUuid" | "projectId" | "name" | "sortOrder" | "boundStatus" | "retention" | "createdAt" | "updatedAt", ExtArgs["result"]["boardColumn"]> +export type BoardColumnInclude = { + project?: boolean | Prisma.ProjectDefaultArgs + tasks?: boolean | Prisma.BoardColumn$tasksArgs + _count?: boolean | Prisma.BoardColumnCountOutputTypeDefaultArgs +} +export type BoardColumnIncludeCreateManyAndReturn = { + project?: boolean | Prisma.ProjectDefaultArgs +} +export type BoardColumnIncludeUpdateManyAndReturn = { + project?: boolean | Prisma.ProjectDefaultArgs +} + +export type $BoardColumnPayload = { + name: "BoardColumn" + objects: { + project: Prisma.$ProjectPayload + tasks: Prisma.$TaskPayload[] + } + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: number + uuid: string + projectUuid: string | null + projectId: number + name: string + sortOrder: number + boundStatus: string | null + retention: string + createdAt: Date + updatedAt: Date + }, ExtArgs["result"]["boardColumn"]> + composites: {} +} + +export type BoardColumnGetPayload = runtime.Types.Result.GetResult + +export type BoardColumnCountArgs = + Omit & { + select?: BoardColumnCountAggregateInputType | true + } + +export interface BoardColumnDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['BoardColumn'], meta: { name: 'BoardColumn' } } + /** + * Find zero or one BoardColumn that matches the filter. + * @param {BoardColumnFindUniqueArgs} args - Arguments to find a BoardColumn + * @example + * // Get one BoardColumn + * const boardColumn = await prisma.boardColumn.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__BoardColumnClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one BoardColumn that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {BoardColumnFindUniqueOrThrowArgs} args - Arguments to find a BoardColumn + * @example + * // Get one BoardColumn + * const boardColumn = await prisma.boardColumn.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__BoardColumnClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first BoardColumn that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {BoardColumnFindFirstArgs} args - Arguments to find a BoardColumn + * @example + * // Get one BoardColumn + * const boardColumn = await prisma.boardColumn.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__BoardColumnClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first BoardColumn that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {BoardColumnFindFirstOrThrowArgs} args - Arguments to find a BoardColumn + * @example + * // Get one BoardColumn + * const boardColumn = await prisma.boardColumn.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__BoardColumnClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more BoardColumns that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {BoardColumnFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all BoardColumns + * const boardColumns = await prisma.boardColumn.findMany() + * + * // Get first 10 BoardColumns + * const boardColumns = await prisma.boardColumn.findMany({ take: 10 }) + * + * // Only select the `id` + * const boardColumnWithIdOnly = await prisma.boardColumn.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a BoardColumn. + * @param {BoardColumnCreateArgs} args - Arguments to create a BoardColumn. + * @example + * // Create one BoardColumn + * const BoardColumn = await prisma.boardColumn.create({ + * data: { + * // ... data to create a BoardColumn + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__BoardColumnClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many BoardColumns. + * @param {BoardColumnCreateManyArgs} args - Arguments to create many BoardColumns. + * @example + * // Create many BoardColumns + * const boardColumn = await prisma.boardColumn.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many BoardColumns and returns the data saved in the database. + * @param {BoardColumnCreateManyAndReturnArgs} args - Arguments to create many BoardColumns. + * @example + * // Create many BoardColumns + * const boardColumn = await prisma.boardColumn.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many BoardColumns and only return the `id` + * const boardColumnWithIdOnly = await prisma.boardColumn.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a BoardColumn. + * @param {BoardColumnDeleteArgs} args - Arguments to delete one BoardColumn. + * @example + * // Delete one BoardColumn + * const BoardColumn = await prisma.boardColumn.delete({ + * where: { + * // ... filter to delete one BoardColumn + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__BoardColumnClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one BoardColumn. + * @param {BoardColumnUpdateArgs} args - Arguments to update one BoardColumn. + * @example + * // Update one BoardColumn + * const boardColumn = await prisma.boardColumn.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__BoardColumnClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more BoardColumns. + * @param {BoardColumnDeleteManyArgs} args - Arguments to filter BoardColumns to delete. + * @example + * // Delete a few BoardColumns + * const { count } = await prisma.boardColumn.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more BoardColumns. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {BoardColumnUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many BoardColumns + * const boardColumn = await prisma.boardColumn.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more BoardColumns and returns the data updated in the database. + * @param {BoardColumnUpdateManyAndReturnArgs} args - Arguments to update many BoardColumns. + * @example + * // Update many BoardColumns + * const boardColumn = await prisma.boardColumn.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more BoardColumns and only return the `id` + * const boardColumnWithIdOnly = await prisma.boardColumn.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one BoardColumn. + * @param {BoardColumnUpsertArgs} args - Arguments to update or create a BoardColumn. + * @example + * // Update or create a BoardColumn + * const boardColumn = await prisma.boardColumn.upsert({ + * create: { + * // ... data to create a BoardColumn + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the BoardColumn we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__BoardColumnClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of BoardColumns. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {BoardColumnCountArgs} args - Arguments to filter BoardColumns to count. + * @example + * // Count the number of BoardColumns + * const count = await prisma.boardColumn.count({ + * where: { + * // ... the filter for the BoardColumns we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a BoardColumn. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {BoardColumnAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by BoardColumn. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {BoardColumnGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends BoardColumnGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: BoardColumnGroupByArgs['orderBy'] } + : { orderBy?: BoardColumnGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetBoardColumnGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the BoardColumn model + */ +readonly fields: BoardColumnFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for BoardColumn. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__BoardColumnClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + project = {}>(args?: Prisma.Subset>): Prisma.Prisma__ProjectClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + tasks = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the BoardColumn model + */ +export interface BoardColumnFieldRefs { + readonly id: Prisma.FieldRef<"BoardColumn", 'Int'> + readonly uuid: Prisma.FieldRef<"BoardColumn", 'String'> + readonly projectUuid: Prisma.FieldRef<"BoardColumn", 'String'> + readonly projectId: Prisma.FieldRef<"BoardColumn", 'Int'> + readonly name: Prisma.FieldRef<"BoardColumn", 'String'> + readonly sortOrder: Prisma.FieldRef<"BoardColumn", 'Int'> + readonly boundStatus: Prisma.FieldRef<"BoardColumn", 'String'> + readonly retention: Prisma.FieldRef<"BoardColumn", 'String'> + readonly createdAt: Prisma.FieldRef<"BoardColumn", 'DateTime'> + readonly updatedAt: Prisma.FieldRef<"BoardColumn", 'DateTime'> +} + + +// Custom InputTypes +/** + * BoardColumn findUnique + */ +export type BoardColumnFindUniqueArgs = { + /** + * Select specific fields to fetch from the BoardColumn + */ + select?: Prisma.BoardColumnSelect | null + /** + * Omit specific fields from the BoardColumn + */ + omit?: Prisma.BoardColumnOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.BoardColumnInclude | null + /** + * Filter, which BoardColumn to fetch. + */ + where: Prisma.BoardColumnWhereUniqueInput +} + +/** + * BoardColumn findUniqueOrThrow + */ +export type BoardColumnFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the BoardColumn + */ + select?: Prisma.BoardColumnSelect | null + /** + * Omit specific fields from the BoardColumn + */ + omit?: Prisma.BoardColumnOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.BoardColumnInclude | null + /** + * Filter, which BoardColumn to fetch. + */ + where: Prisma.BoardColumnWhereUniqueInput +} + +/** + * BoardColumn findFirst + */ +export type BoardColumnFindFirstArgs = { + /** + * Select specific fields to fetch from the BoardColumn + */ + select?: Prisma.BoardColumnSelect | null + /** + * Omit specific fields from the BoardColumn + */ + omit?: Prisma.BoardColumnOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.BoardColumnInclude | null + /** + * Filter, which BoardColumn to fetch. + */ + where?: Prisma.BoardColumnWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of BoardColumns to fetch. + */ + orderBy?: Prisma.BoardColumnOrderByWithRelationInput | Prisma.BoardColumnOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for BoardColumns. + */ + cursor?: Prisma.BoardColumnWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` BoardColumns from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` BoardColumns. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of BoardColumns. + */ + distinct?: Prisma.BoardColumnScalarFieldEnum | Prisma.BoardColumnScalarFieldEnum[] +} + +/** + * BoardColumn findFirstOrThrow + */ +export type BoardColumnFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the BoardColumn + */ + select?: Prisma.BoardColumnSelect | null + /** + * Omit specific fields from the BoardColumn + */ + omit?: Prisma.BoardColumnOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.BoardColumnInclude | null + /** + * Filter, which BoardColumn to fetch. + */ + where?: Prisma.BoardColumnWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of BoardColumns to fetch. + */ + orderBy?: Prisma.BoardColumnOrderByWithRelationInput | Prisma.BoardColumnOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for BoardColumns. + */ + cursor?: Prisma.BoardColumnWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` BoardColumns from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` BoardColumns. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of BoardColumns. + */ + distinct?: Prisma.BoardColumnScalarFieldEnum | Prisma.BoardColumnScalarFieldEnum[] +} + +/** + * BoardColumn findMany + */ +export type BoardColumnFindManyArgs = { + /** + * Select specific fields to fetch from the BoardColumn + */ + select?: Prisma.BoardColumnSelect | null + /** + * Omit specific fields from the BoardColumn + */ + omit?: Prisma.BoardColumnOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.BoardColumnInclude | null + /** + * Filter, which BoardColumns to fetch. + */ + where?: Prisma.BoardColumnWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of BoardColumns to fetch. + */ + orderBy?: Prisma.BoardColumnOrderByWithRelationInput | Prisma.BoardColumnOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing BoardColumns. + */ + cursor?: Prisma.BoardColumnWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` BoardColumns from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` BoardColumns. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of BoardColumns. + */ + distinct?: Prisma.BoardColumnScalarFieldEnum | Prisma.BoardColumnScalarFieldEnum[] +} + +/** + * BoardColumn create + */ +export type BoardColumnCreateArgs = { + /** + * Select specific fields to fetch from the BoardColumn + */ + select?: Prisma.BoardColumnSelect | null + /** + * Omit specific fields from the BoardColumn + */ + omit?: Prisma.BoardColumnOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.BoardColumnInclude | null + /** + * The data needed to create a BoardColumn. + */ + data: Prisma.XOR +} + +/** + * BoardColumn createMany + */ +export type BoardColumnCreateManyArgs = { + /** + * The data used to create many BoardColumns. + */ + data: Prisma.BoardColumnCreateManyInput | Prisma.BoardColumnCreateManyInput[] + skipDuplicates?: boolean +} + +/** + * BoardColumn createManyAndReturn + */ +export type BoardColumnCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the BoardColumn + */ + select?: Prisma.BoardColumnSelectCreateManyAndReturn | null + /** + * Omit specific fields from the BoardColumn + */ + omit?: Prisma.BoardColumnOmit | null + /** + * The data used to create many BoardColumns. + */ + data: Prisma.BoardColumnCreateManyInput | Prisma.BoardColumnCreateManyInput[] + skipDuplicates?: boolean + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.BoardColumnIncludeCreateManyAndReturn | null +} + +/** + * BoardColumn update + */ +export type BoardColumnUpdateArgs = { + /** + * Select specific fields to fetch from the BoardColumn + */ + select?: Prisma.BoardColumnSelect | null + /** + * Omit specific fields from the BoardColumn + */ + omit?: Prisma.BoardColumnOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.BoardColumnInclude | null + /** + * The data needed to update a BoardColumn. + */ + data: Prisma.XOR + /** + * Choose, which BoardColumn to update. + */ + where: Prisma.BoardColumnWhereUniqueInput +} + +/** + * BoardColumn updateMany + */ +export type BoardColumnUpdateManyArgs = { + /** + * The data used to update BoardColumns. + */ + data: Prisma.XOR + /** + * Filter which BoardColumns to update + */ + where?: Prisma.BoardColumnWhereInput + /** + * Limit how many BoardColumns to update. + */ + limit?: number +} + +/** + * BoardColumn updateManyAndReturn + */ +export type BoardColumnUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the BoardColumn + */ + select?: Prisma.BoardColumnSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the BoardColumn + */ + omit?: Prisma.BoardColumnOmit | null + /** + * The data used to update BoardColumns. + */ + data: Prisma.XOR + /** + * Filter which BoardColumns to update + */ + where?: Prisma.BoardColumnWhereInput + /** + * Limit how many BoardColumns to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.BoardColumnIncludeUpdateManyAndReturn | null +} + +/** + * BoardColumn upsert + */ +export type BoardColumnUpsertArgs = { + /** + * Select specific fields to fetch from the BoardColumn + */ + select?: Prisma.BoardColumnSelect | null + /** + * Omit specific fields from the BoardColumn + */ + omit?: Prisma.BoardColumnOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.BoardColumnInclude | null + /** + * The filter to search for the BoardColumn to update in case it exists. + */ + where: Prisma.BoardColumnWhereUniqueInput + /** + * In case the BoardColumn found by the `where` argument doesn't exist, create a new BoardColumn with this data. + */ + create: Prisma.XOR + /** + * In case the BoardColumn was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * BoardColumn delete + */ +export type BoardColumnDeleteArgs = { + /** + * Select specific fields to fetch from the BoardColumn + */ + select?: Prisma.BoardColumnSelect | null + /** + * Omit specific fields from the BoardColumn + */ + omit?: Prisma.BoardColumnOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.BoardColumnInclude | null + /** + * Filter which BoardColumn to delete. + */ + where: Prisma.BoardColumnWhereUniqueInput +} + +/** + * BoardColumn deleteMany + */ +export type BoardColumnDeleteManyArgs = { + /** + * Filter which BoardColumns to delete + */ + where?: Prisma.BoardColumnWhereInput + /** + * Limit how many BoardColumns to delete. + */ + limit?: number +} + +/** + * BoardColumn.tasks + */ +export type BoardColumn$tasksArgs = { + /** + * Select specific fields to fetch from the Task + */ + select?: Prisma.TaskSelect | null + /** + * Omit specific fields from the Task + */ + omit?: Prisma.TaskOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskInclude | null + where?: Prisma.TaskWhereInput + orderBy?: Prisma.TaskOrderByWithRelationInput | Prisma.TaskOrderByWithRelationInput[] + cursor?: Prisma.TaskWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.TaskScalarFieldEnum | Prisma.TaskScalarFieldEnum[] +} + +/** + * BoardColumn without action + */ +export type BoardColumnDefaultArgs = { + /** + * Select specific fields to fetch from the BoardColumn + */ + select?: Prisma.BoardColumnSelect | null + /** + * Omit specific fields from the BoardColumn + */ + omit?: Prisma.BoardColumnOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.BoardColumnInclude | null +} diff --git a/generated/prisma/models/CheckIn.ts b/generated/prisma/models/CheckIn.ts new file mode 100644 index 0000000..9dec467 --- /dev/null +++ b/generated/prisma/models/CheckIn.ts @@ -0,0 +1,1650 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `CheckIn` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums.ts" +import type * as Prisma from "../internal/prismaNamespace.ts" + +/** + * Model CheckIn + * + */ +export type CheckInModel = runtime.Types.Result.DefaultSelection + +export type AggregateCheckIn = { + _count: CheckInCountAggregateOutputType | null + _avg: CheckInAvgAggregateOutputType | null + _sum: CheckInSumAggregateOutputType | null + _min: CheckInMinAggregateOutputType | null + _max: CheckInMaxAggregateOutputType | null +} + +export type CheckInAvgAggregateOutputType = { + id: number | null + habitId: number | null + userId: number | null + seq: number | null +} + +export type CheckInSumAggregateOutputType = { + id: number | null + habitId: number | null + userId: number | null + seq: number | null +} + +export type CheckInMinAggregateOutputType = { + id: number | null + habitId: number | null + userId: number | null + date: string | null + seq: number | null + note: string | null + source: string | null + createdAt: Date | null +} + +export type CheckInMaxAggregateOutputType = { + id: number | null + habitId: number | null + userId: number | null + date: string | null + seq: number | null + note: string | null + source: string | null + createdAt: Date | null +} + +export type CheckInCountAggregateOutputType = { + id: number + habitId: number + userId: number + date: number + seq: number + note: number + source: number + createdAt: number + _all: number +} + + +export type CheckInAvgAggregateInputType = { + id?: true + habitId?: true + userId?: true + seq?: true +} + +export type CheckInSumAggregateInputType = { + id?: true + habitId?: true + userId?: true + seq?: true +} + +export type CheckInMinAggregateInputType = { + id?: true + habitId?: true + userId?: true + date?: true + seq?: true + note?: true + source?: true + createdAt?: true +} + +export type CheckInMaxAggregateInputType = { + id?: true + habitId?: true + userId?: true + date?: true + seq?: true + note?: true + source?: true + createdAt?: true +} + +export type CheckInCountAggregateInputType = { + id?: true + habitId?: true + userId?: true + date?: true + seq?: true + note?: true + source?: true + createdAt?: true + _all?: true +} + +export type CheckInAggregateArgs = { + /** + * Filter which CheckIn to aggregate. + */ + where?: Prisma.CheckInWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CheckIns to fetch. + */ + orderBy?: Prisma.CheckInOrderByWithRelationInput | Prisma.CheckInOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.CheckInWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CheckIns from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CheckIns. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned CheckIns + **/ + _count?: true | CheckInCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to average + **/ + _avg?: CheckInAvgAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to sum + **/ + _sum?: CheckInSumAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: CheckInMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: CheckInMaxAggregateInputType +} + +export type GetCheckInAggregateType = { + [P in keyof T & keyof AggregateCheckIn]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type CheckInGroupByArgs = { + where?: Prisma.CheckInWhereInput + orderBy?: Prisma.CheckInOrderByWithAggregationInput | Prisma.CheckInOrderByWithAggregationInput[] + by: Prisma.CheckInScalarFieldEnum[] | Prisma.CheckInScalarFieldEnum + having?: Prisma.CheckInScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: CheckInCountAggregateInputType | true + _avg?: CheckInAvgAggregateInputType + _sum?: CheckInSumAggregateInputType + _min?: CheckInMinAggregateInputType + _max?: CheckInMaxAggregateInputType +} + +export type CheckInGroupByOutputType = { + id: number + habitId: number + userId: number + date: string + seq: number + note: string + source: string + createdAt: Date + _count: CheckInCountAggregateOutputType | null + _avg: CheckInAvgAggregateOutputType | null + _sum: CheckInSumAggregateOutputType | null + _min: CheckInMinAggregateOutputType | null + _max: CheckInMaxAggregateOutputType | null +} + +export type GetCheckInGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof CheckInGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type CheckInWhereInput = { + AND?: Prisma.CheckInWhereInput | Prisma.CheckInWhereInput[] + OR?: Prisma.CheckInWhereInput[] + NOT?: Prisma.CheckInWhereInput | Prisma.CheckInWhereInput[] + id?: Prisma.IntFilter<"CheckIn"> | number + habitId?: Prisma.IntFilter<"CheckIn"> | number + userId?: Prisma.IntFilter<"CheckIn"> | number + date?: Prisma.StringFilter<"CheckIn"> | string + seq?: Prisma.IntFilter<"CheckIn"> | number + note?: Prisma.StringFilter<"CheckIn"> | string + source?: Prisma.StringFilter<"CheckIn"> | string + createdAt?: Prisma.DateTimeFilter<"CheckIn"> | Date | string + habit?: Prisma.XOR + user?: Prisma.XOR +} + +export type CheckInOrderByWithRelationInput = { + id?: Prisma.SortOrder + habitId?: Prisma.SortOrder + userId?: Prisma.SortOrder + date?: Prisma.SortOrder + seq?: Prisma.SortOrder + note?: Prisma.SortOrder + source?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + habit?: Prisma.HabitOrderByWithRelationInput + user?: Prisma.UserOrderByWithRelationInput +} + +export type CheckInWhereUniqueInput = Prisma.AtLeast<{ + id?: number + habitId_date_seq?: Prisma.CheckInHabitIdDateSeqCompoundUniqueInput + AND?: Prisma.CheckInWhereInput | Prisma.CheckInWhereInput[] + OR?: Prisma.CheckInWhereInput[] + NOT?: Prisma.CheckInWhereInput | Prisma.CheckInWhereInput[] + habitId?: Prisma.IntFilter<"CheckIn"> | number + userId?: Prisma.IntFilter<"CheckIn"> | number + date?: Prisma.StringFilter<"CheckIn"> | string + seq?: Prisma.IntFilter<"CheckIn"> | number + note?: Prisma.StringFilter<"CheckIn"> | string + source?: Prisma.StringFilter<"CheckIn"> | string + createdAt?: Prisma.DateTimeFilter<"CheckIn"> | Date | string + habit?: Prisma.XOR + user?: Prisma.XOR +}, "id" | "habitId_date_seq"> + +export type CheckInOrderByWithAggregationInput = { + id?: Prisma.SortOrder + habitId?: Prisma.SortOrder + userId?: Prisma.SortOrder + date?: Prisma.SortOrder + seq?: Prisma.SortOrder + note?: Prisma.SortOrder + source?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + _count?: Prisma.CheckInCountOrderByAggregateInput + _avg?: Prisma.CheckInAvgOrderByAggregateInput + _max?: Prisma.CheckInMaxOrderByAggregateInput + _min?: Prisma.CheckInMinOrderByAggregateInput + _sum?: Prisma.CheckInSumOrderByAggregateInput +} + +export type CheckInScalarWhereWithAggregatesInput = { + AND?: Prisma.CheckInScalarWhereWithAggregatesInput | Prisma.CheckInScalarWhereWithAggregatesInput[] + OR?: Prisma.CheckInScalarWhereWithAggregatesInput[] + NOT?: Prisma.CheckInScalarWhereWithAggregatesInput | Prisma.CheckInScalarWhereWithAggregatesInput[] + id?: Prisma.IntWithAggregatesFilter<"CheckIn"> | number + habitId?: Prisma.IntWithAggregatesFilter<"CheckIn"> | number + userId?: Prisma.IntWithAggregatesFilter<"CheckIn"> | number + date?: Prisma.StringWithAggregatesFilter<"CheckIn"> | string + seq?: Prisma.IntWithAggregatesFilter<"CheckIn"> | number + note?: Prisma.StringWithAggregatesFilter<"CheckIn"> | string + source?: Prisma.StringWithAggregatesFilter<"CheckIn"> | string + createdAt?: Prisma.DateTimeWithAggregatesFilter<"CheckIn"> | Date | string +} + +export type CheckInCreateInput = { + date: string + seq?: number + note?: string + source?: string + createdAt?: Date | string + habit: Prisma.HabitCreateNestedOneWithoutRecordsInput + user: Prisma.UserCreateNestedOneWithoutCheckInsInput +} + +export type CheckInUncheckedCreateInput = { + id?: number + habitId: number + userId: number + date: string + seq?: number + note?: string + source?: string + createdAt?: Date | string +} + +export type CheckInUpdateInput = { + date?: Prisma.StringFieldUpdateOperationsInput | string + seq?: Prisma.IntFieldUpdateOperationsInput | number + note?: Prisma.StringFieldUpdateOperationsInput | string + source?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habit?: Prisma.HabitUpdateOneRequiredWithoutRecordsNestedInput + user?: Prisma.UserUpdateOneRequiredWithoutCheckInsNestedInput +} + +export type CheckInUncheckedUpdateInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + habitId?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.IntFieldUpdateOperationsInput | number + date?: Prisma.StringFieldUpdateOperationsInput | string + seq?: Prisma.IntFieldUpdateOperationsInput | number + note?: Prisma.StringFieldUpdateOperationsInput | string + source?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CheckInCreateManyInput = { + id?: number + habitId: number + userId: number + date: string + seq?: number + note?: string + source?: string + createdAt?: Date | string +} + +export type CheckInUpdateManyMutationInput = { + date?: Prisma.StringFieldUpdateOperationsInput | string + seq?: Prisma.IntFieldUpdateOperationsInput | number + note?: Prisma.StringFieldUpdateOperationsInput | string + source?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CheckInUncheckedUpdateManyInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + habitId?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.IntFieldUpdateOperationsInput | number + date?: Prisma.StringFieldUpdateOperationsInput | string + seq?: Prisma.IntFieldUpdateOperationsInput | number + note?: Prisma.StringFieldUpdateOperationsInput | string + source?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CheckInListRelationFilter = { + every?: Prisma.CheckInWhereInput + some?: Prisma.CheckInWhereInput + none?: Prisma.CheckInWhereInput +} + +export type CheckInOrderByRelationAggregateInput = { + _count?: Prisma.SortOrder +} + +export type CheckInHabitIdDateSeqCompoundUniqueInput = { + habitId: number + date: string + seq: number +} + +export type CheckInCountOrderByAggregateInput = { + id?: Prisma.SortOrder + habitId?: Prisma.SortOrder + userId?: Prisma.SortOrder + date?: Prisma.SortOrder + seq?: Prisma.SortOrder + note?: Prisma.SortOrder + source?: Prisma.SortOrder + createdAt?: Prisma.SortOrder +} + +export type CheckInAvgOrderByAggregateInput = { + id?: Prisma.SortOrder + habitId?: Prisma.SortOrder + userId?: Prisma.SortOrder + seq?: Prisma.SortOrder +} + +export type CheckInMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + habitId?: Prisma.SortOrder + userId?: Prisma.SortOrder + date?: Prisma.SortOrder + seq?: Prisma.SortOrder + note?: Prisma.SortOrder + source?: Prisma.SortOrder + createdAt?: Prisma.SortOrder +} + +export type CheckInMinOrderByAggregateInput = { + id?: Prisma.SortOrder + habitId?: Prisma.SortOrder + userId?: Prisma.SortOrder + date?: Prisma.SortOrder + seq?: Prisma.SortOrder + note?: Prisma.SortOrder + source?: Prisma.SortOrder + createdAt?: Prisma.SortOrder +} + +export type CheckInSumOrderByAggregateInput = { + id?: Prisma.SortOrder + habitId?: Prisma.SortOrder + userId?: Prisma.SortOrder + seq?: Prisma.SortOrder +} + +export type CheckInCreateNestedManyWithoutUserInput = { + create?: Prisma.XOR | Prisma.CheckInCreateWithoutUserInput[] | Prisma.CheckInUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.CheckInCreateOrConnectWithoutUserInput | Prisma.CheckInCreateOrConnectWithoutUserInput[] + createMany?: Prisma.CheckInCreateManyUserInputEnvelope + connect?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] +} + +export type CheckInUncheckedCreateNestedManyWithoutUserInput = { + create?: Prisma.XOR | Prisma.CheckInCreateWithoutUserInput[] | Prisma.CheckInUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.CheckInCreateOrConnectWithoutUserInput | Prisma.CheckInCreateOrConnectWithoutUserInput[] + createMany?: Prisma.CheckInCreateManyUserInputEnvelope + connect?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] +} + +export type CheckInUpdateManyWithoutUserNestedInput = { + create?: Prisma.XOR | Prisma.CheckInCreateWithoutUserInput[] | Prisma.CheckInUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.CheckInCreateOrConnectWithoutUserInput | Prisma.CheckInCreateOrConnectWithoutUserInput[] + upsert?: Prisma.CheckInUpsertWithWhereUniqueWithoutUserInput | Prisma.CheckInUpsertWithWhereUniqueWithoutUserInput[] + createMany?: Prisma.CheckInCreateManyUserInputEnvelope + set?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] + disconnect?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] + delete?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] + connect?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] + update?: Prisma.CheckInUpdateWithWhereUniqueWithoutUserInput | Prisma.CheckInUpdateWithWhereUniqueWithoutUserInput[] + updateMany?: Prisma.CheckInUpdateManyWithWhereWithoutUserInput | Prisma.CheckInUpdateManyWithWhereWithoutUserInput[] + deleteMany?: Prisma.CheckInScalarWhereInput | Prisma.CheckInScalarWhereInput[] +} + +export type CheckInUncheckedUpdateManyWithoutUserNestedInput = { + create?: Prisma.XOR | Prisma.CheckInCreateWithoutUserInput[] | Prisma.CheckInUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.CheckInCreateOrConnectWithoutUserInput | Prisma.CheckInCreateOrConnectWithoutUserInput[] + upsert?: Prisma.CheckInUpsertWithWhereUniqueWithoutUserInput | Prisma.CheckInUpsertWithWhereUniqueWithoutUserInput[] + createMany?: Prisma.CheckInCreateManyUserInputEnvelope + set?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] + disconnect?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] + delete?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] + connect?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] + update?: Prisma.CheckInUpdateWithWhereUniqueWithoutUserInput | Prisma.CheckInUpdateWithWhereUniqueWithoutUserInput[] + updateMany?: Prisma.CheckInUpdateManyWithWhereWithoutUserInput | Prisma.CheckInUpdateManyWithWhereWithoutUserInput[] + deleteMany?: Prisma.CheckInScalarWhereInput | Prisma.CheckInScalarWhereInput[] +} + +export type CheckInCreateNestedManyWithoutHabitInput = { + create?: Prisma.XOR | Prisma.CheckInCreateWithoutHabitInput[] | Prisma.CheckInUncheckedCreateWithoutHabitInput[] + connectOrCreate?: Prisma.CheckInCreateOrConnectWithoutHabitInput | Prisma.CheckInCreateOrConnectWithoutHabitInput[] + createMany?: Prisma.CheckInCreateManyHabitInputEnvelope + connect?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] +} + +export type CheckInUncheckedCreateNestedManyWithoutHabitInput = { + create?: Prisma.XOR | Prisma.CheckInCreateWithoutHabitInput[] | Prisma.CheckInUncheckedCreateWithoutHabitInput[] + connectOrCreate?: Prisma.CheckInCreateOrConnectWithoutHabitInput | Prisma.CheckInCreateOrConnectWithoutHabitInput[] + createMany?: Prisma.CheckInCreateManyHabitInputEnvelope + connect?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] +} + +export type CheckInUpdateManyWithoutHabitNestedInput = { + create?: Prisma.XOR | Prisma.CheckInCreateWithoutHabitInput[] | Prisma.CheckInUncheckedCreateWithoutHabitInput[] + connectOrCreate?: Prisma.CheckInCreateOrConnectWithoutHabitInput | Prisma.CheckInCreateOrConnectWithoutHabitInput[] + upsert?: Prisma.CheckInUpsertWithWhereUniqueWithoutHabitInput | Prisma.CheckInUpsertWithWhereUniqueWithoutHabitInput[] + createMany?: Prisma.CheckInCreateManyHabitInputEnvelope + set?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] + disconnect?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] + delete?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] + connect?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] + update?: Prisma.CheckInUpdateWithWhereUniqueWithoutHabitInput | Prisma.CheckInUpdateWithWhereUniqueWithoutHabitInput[] + updateMany?: Prisma.CheckInUpdateManyWithWhereWithoutHabitInput | Prisma.CheckInUpdateManyWithWhereWithoutHabitInput[] + deleteMany?: Prisma.CheckInScalarWhereInput | Prisma.CheckInScalarWhereInput[] +} + +export type CheckInUncheckedUpdateManyWithoutHabitNestedInput = { + create?: Prisma.XOR | Prisma.CheckInCreateWithoutHabitInput[] | Prisma.CheckInUncheckedCreateWithoutHabitInput[] + connectOrCreate?: Prisma.CheckInCreateOrConnectWithoutHabitInput | Prisma.CheckInCreateOrConnectWithoutHabitInput[] + upsert?: Prisma.CheckInUpsertWithWhereUniqueWithoutHabitInput | Prisma.CheckInUpsertWithWhereUniqueWithoutHabitInput[] + createMany?: Prisma.CheckInCreateManyHabitInputEnvelope + set?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] + disconnect?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] + delete?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] + connect?: Prisma.CheckInWhereUniqueInput | Prisma.CheckInWhereUniqueInput[] + update?: Prisma.CheckInUpdateWithWhereUniqueWithoutHabitInput | Prisma.CheckInUpdateWithWhereUniqueWithoutHabitInput[] + updateMany?: Prisma.CheckInUpdateManyWithWhereWithoutHabitInput | Prisma.CheckInUpdateManyWithWhereWithoutHabitInput[] + deleteMany?: Prisma.CheckInScalarWhereInput | Prisma.CheckInScalarWhereInput[] +} + +export type CheckInCreateWithoutUserInput = { + date: string + seq?: number + note?: string + source?: string + createdAt?: Date | string + habit: Prisma.HabitCreateNestedOneWithoutRecordsInput +} + +export type CheckInUncheckedCreateWithoutUserInput = { + id?: number + habitId: number + date: string + seq?: number + note?: string + source?: string + createdAt?: Date | string +} + +export type CheckInCreateOrConnectWithoutUserInput = { + where: Prisma.CheckInWhereUniqueInput + create: Prisma.XOR +} + +export type CheckInCreateManyUserInputEnvelope = { + data: Prisma.CheckInCreateManyUserInput | Prisma.CheckInCreateManyUserInput[] + skipDuplicates?: boolean +} + +export type CheckInUpsertWithWhereUniqueWithoutUserInput = { + where: Prisma.CheckInWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type CheckInUpdateWithWhereUniqueWithoutUserInput = { + where: Prisma.CheckInWhereUniqueInput + data: Prisma.XOR +} + +export type CheckInUpdateManyWithWhereWithoutUserInput = { + where: Prisma.CheckInScalarWhereInput + data: Prisma.XOR +} + +export type CheckInScalarWhereInput = { + AND?: Prisma.CheckInScalarWhereInput | Prisma.CheckInScalarWhereInput[] + OR?: Prisma.CheckInScalarWhereInput[] + NOT?: Prisma.CheckInScalarWhereInput | Prisma.CheckInScalarWhereInput[] + id?: Prisma.IntFilter<"CheckIn"> | number + habitId?: Prisma.IntFilter<"CheckIn"> | number + userId?: Prisma.IntFilter<"CheckIn"> | number + date?: Prisma.StringFilter<"CheckIn"> | string + seq?: Prisma.IntFilter<"CheckIn"> | number + note?: Prisma.StringFilter<"CheckIn"> | string + source?: Prisma.StringFilter<"CheckIn"> | string + createdAt?: Prisma.DateTimeFilter<"CheckIn"> | Date | string +} + +export type CheckInCreateWithoutHabitInput = { + date: string + seq?: number + note?: string + source?: string + createdAt?: Date | string + user: Prisma.UserCreateNestedOneWithoutCheckInsInput +} + +export type CheckInUncheckedCreateWithoutHabitInput = { + id?: number + userId: number + date: string + seq?: number + note?: string + source?: string + createdAt?: Date | string +} + +export type CheckInCreateOrConnectWithoutHabitInput = { + where: Prisma.CheckInWhereUniqueInput + create: Prisma.XOR +} + +export type CheckInCreateManyHabitInputEnvelope = { + data: Prisma.CheckInCreateManyHabitInput | Prisma.CheckInCreateManyHabitInput[] + skipDuplicates?: boolean +} + +export type CheckInUpsertWithWhereUniqueWithoutHabitInput = { + where: Prisma.CheckInWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type CheckInUpdateWithWhereUniqueWithoutHabitInput = { + where: Prisma.CheckInWhereUniqueInput + data: Prisma.XOR +} + +export type CheckInUpdateManyWithWhereWithoutHabitInput = { + where: Prisma.CheckInScalarWhereInput + data: Prisma.XOR +} + +export type CheckInCreateManyUserInput = { + id?: number + habitId: number + date: string + seq?: number + note?: string + source?: string + createdAt?: Date | string +} + +export type CheckInUpdateWithoutUserInput = { + date?: Prisma.StringFieldUpdateOperationsInput | string + seq?: Prisma.IntFieldUpdateOperationsInput | number + note?: Prisma.StringFieldUpdateOperationsInput | string + source?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habit?: Prisma.HabitUpdateOneRequiredWithoutRecordsNestedInput +} + +export type CheckInUncheckedUpdateWithoutUserInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + habitId?: Prisma.IntFieldUpdateOperationsInput | number + date?: Prisma.StringFieldUpdateOperationsInput | string + seq?: Prisma.IntFieldUpdateOperationsInput | number + note?: Prisma.StringFieldUpdateOperationsInput | string + source?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CheckInUncheckedUpdateManyWithoutUserInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + habitId?: Prisma.IntFieldUpdateOperationsInput | number + date?: Prisma.StringFieldUpdateOperationsInput | string + seq?: Prisma.IntFieldUpdateOperationsInput | number + note?: Prisma.StringFieldUpdateOperationsInput | string + source?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CheckInCreateManyHabitInput = { + id?: number + userId: number + date: string + seq?: number + note?: string + source?: string + createdAt?: Date | string +} + +export type CheckInUpdateWithoutHabitInput = { + date?: Prisma.StringFieldUpdateOperationsInput | string + seq?: Prisma.IntFieldUpdateOperationsInput | number + note?: Prisma.StringFieldUpdateOperationsInput | string + source?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + user?: Prisma.UserUpdateOneRequiredWithoutCheckInsNestedInput +} + +export type CheckInUncheckedUpdateWithoutHabitInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.IntFieldUpdateOperationsInput | number + date?: Prisma.StringFieldUpdateOperationsInput | string + seq?: Prisma.IntFieldUpdateOperationsInput | number + note?: Prisma.StringFieldUpdateOperationsInput | string + source?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CheckInUncheckedUpdateManyWithoutHabitInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.IntFieldUpdateOperationsInput | number + date?: Prisma.StringFieldUpdateOperationsInput | string + seq?: Prisma.IntFieldUpdateOperationsInput | number + note?: Prisma.StringFieldUpdateOperationsInput | string + source?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + + + +export type CheckInSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + habitId?: boolean + userId?: boolean + date?: boolean + seq?: boolean + note?: boolean + source?: boolean + createdAt?: boolean + habit?: boolean | Prisma.HabitDefaultArgs + user?: boolean | Prisma.UserDefaultArgs +}, ExtArgs["result"]["checkIn"]> + +export type CheckInSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + habitId?: boolean + userId?: boolean + date?: boolean + seq?: boolean + note?: boolean + source?: boolean + createdAt?: boolean + habit?: boolean | Prisma.HabitDefaultArgs + user?: boolean | Prisma.UserDefaultArgs +}, ExtArgs["result"]["checkIn"]> + +export type CheckInSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + habitId?: boolean + userId?: boolean + date?: boolean + seq?: boolean + note?: boolean + source?: boolean + createdAt?: boolean + habit?: boolean | Prisma.HabitDefaultArgs + user?: boolean | Prisma.UserDefaultArgs +}, ExtArgs["result"]["checkIn"]> + +export type CheckInSelectScalar = { + id?: boolean + habitId?: boolean + userId?: boolean + date?: boolean + seq?: boolean + note?: boolean + source?: boolean + createdAt?: boolean +} + +export type CheckInOmit = runtime.Types.Extensions.GetOmit<"id" | "habitId" | "userId" | "date" | "seq" | "note" | "source" | "createdAt", ExtArgs["result"]["checkIn"]> +export type CheckInInclude = { + habit?: boolean | Prisma.HabitDefaultArgs + user?: boolean | Prisma.UserDefaultArgs +} +export type CheckInIncludeCreateManyAndReturn = { + habit?: boolean | Prisma.HabitDefaultArgs + user?: boolean | Prisma.UserDefaultArgs +} +export type CheckInIncludeUpdateManyAndReturn = { + habit?: boolean | Prisma.HabitDefaultArgs + user?: boolean | Prisma.UserDefaultArgs +} + +export type $CheckInPayload = { + name: "CheckIn" + objects: { + habit: Prisma.$HabitPayload + user: Prisma.$UserPayload + } + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: number + habitId: number + userId: number + date: string + seq: number + note: string + source: string + createdAt: Date + }, ExtArgs["result"]["checkIn"]> + composites: {} +} + +export type CheckInGetPayload = runtime.Types.Result.GetResult + +export type CheckInCountArgs = + Omit & { + select?: CheckInCountAggregateInputType | true + } + +export interface CheckInDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['CheckIn'], meta: { name: 'CheckIn' } } + /** + * Find zero or one CheckIn that matches the filter. + * @param {CheckInFindUniqueArgs} args - Arguments to find a CheckIn + * @example + * // Get one CheckIn + * const checkIn = await prisma.checkIn.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__CheckInClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one CheckIn that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {CheckInFindUniqueOrThrowArgs} args - Arguments to find a CheckIn + * @example + * // Get one CheckIn + * const checkIn = await prisma.checkIn.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__CheckInClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first CheckIn that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckInFindFirstArgs} args - Arguments to find a CheckIn + * @example + * // Get one CheckIn + * const checkIn = await prisma.checkIn.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__CheckInClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first CheckIn that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckInFindFirstOrThrowArgs} args - Arguments to find a CheckIn + * @example + * // Get one CheckIn + * const checkIn = await prisma.checkIn.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__CheckInClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more CheckIns that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckInFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all CheckIns + * const checkIns = await prisma.checkIn.findMany() + * + * // Get first 10 CheckIns + * const checkIns = await prisma.checkIn.findMany({ take: 10 }) + * + * // Only select the `id` + * const checkInWithIdOnly = await prisma.checkIn.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a CheckIn. + * @param {CheckInCreateArgs} args - Arguments to create a CheckIn. + * @example + * // Create one CheckIn + * const CheckIn = await prisma.checkIn.create({ + * data: { + * // ... data to create a CheckIn + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__CheckInClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many CheckIns. + * @param {CheckInCreateManyArgs} args - Arguments to create many CheckIns. + * @example + * // Create many CheckIns + * const checkIn = await prisma.checkIn.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many CheckIns and returns the data saved in the database. + * @param {CheckInCreateManyAndReturnArgs} args - Arguments to create many CheckIns. + * @example + * // Create many CheckIns + * const checkIn = await prisma.checkIn.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many CheckIns and only return the `id` + * const checkInWithIdOnly = await prisma.checkIn.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a CheckIn. + * @param {CheckInDeleteArgs} args - Arguments to delete one CheckIn. + * @example + * // Delete one CheckIn + * const CheckIn = await prisma.checkIn.delete({ + * where: { + * // ... filter to delete one CheckIn + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__CheckInClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one CheckIn. + * @param {CheckInUpdateArgs} args - Arguments to update one CheckIn. + * @example + * // Update one CheckIn + * const checkIn = await prisma.checkIn.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__CheckInClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more CheckIns. + * @param {CheckInDeleteManyArgs} args - Arguments to filter CheckIns to delete. + * @example + * // Delete a few CheckIns + * const { count } = await prisma.checkIn.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more CheckIns. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckInUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many CheckIns + * const checkIn = await prisma.checkIn.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more CheckIns and returns the data updated in the database. + * @param {CheckInUpdateManyAndReturnArgs} args - Arguments to update many CheckIns. + * @example + * // Update many CheckIns + * const checkIn = await prisma.checkIn.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more CheckIns and only return the `id` + * const checkInWithIdOnly = await prisma.checkIn.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one CheckIn. + * @param {CheckInUpsertArgs} args - Arguments to update or create a CheckIn. + * @example + * // Update or create a CheckIn + * const checkIn = await prisma.checkIn.upsert({ + * create: { + * // ... data to create a CheckIn + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the CheckIn we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__CheckInClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of CheckIns. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckInCountArgs} args - Arguments to filter CheckIns to count. + * @example + * // Count the number of CheckIns + * const count = await prisma.checkIn.count({ + * where: { + * // ... the filter for the CheckIns we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a CheckIn. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckInAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by CheckIn. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckInGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends CheckInGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: CheckInGroupByArgs['orderBy'] } + : { orderBy?: CheckInGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetCheckInGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the CheckIn model + */ +readonly fields: CheckInFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for CheckIn. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__CheckInClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + habit = {}>(args?: Prisma.Subset>): Prisma.Prisma__HabitClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + user = {}>(args?: Prisma.Subset>): Prisma.Prisma__UserClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the CheckIn model + */ +export interface CheckInFieldRefs { + readonly id: Prisma.FieldRef<"CheckIn", 'Int'> + readonly habitId: Prisma.FieldRef<"CheckIn", 'Int'> + readonly userId: Prisma.FieldRef<"CheckIn", 'Int'> + readonly date: Prisma.FieldRef<"CheckIn", 'String'> + readonly seq: Prisma.FieldRef<"CheckIn", 'Int'> + readonly note: Prisma.FieldRef<"CheckIn", 'String'> + readonly source: Prisma.FieldRef<"CheckIn", 'String'> + readonly createdAt: Prisma.FieldRef<"CheckIn", 'DateTime'> +} + + +// Custom InputTypes +/** + * CheckIn findUnique + */ +export type CheckInFindUniqueArgs = { + /** + * Select specific fields to fetch from the CheckIn + */ + select?: Prisma.CheckInSelect | null + /** + * Omit specific fields from the CheckIn + */ + omit?: Prisma.CheckInOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckInInclude | null + /** + * Filter, which CheckIn to fetch. + */ + where: Prisma.CheckInWhereUniqueInput +} + +/** + * CheckIn findUniqueOrThrow + */ +export type CheckInFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the CheckIn + */ + select?: Prisma.CheckInSelect | null + /** + * Omit specific fields from the CheckIn + */ + omit?: Prisma.CheckInOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckInInclude | null + /** + * Filter, which CheckIn to fetch. + */ + where: Prisma.CheckInWhereUniqueInput +} + +/** + * CheckIn findFirst + */ +export type CheckInFindFirstArgs = { + /** + * Select specific fields to fetch from the CheckIn + */ + select?: Prisma.CheckInSelect | null + /** + * Omit specific fields from the CheckIn + */ + omit?: Prisma.CheckInOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckInInclude | null + /** + * Filter, which CheckIn to fetch. + */ + where?: Prisma.CheckInWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CheckIns to fetch. + */ + orderBy?: Prisma.CheckInOrderByWithRelationInput | Prisma.CheckInOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for CheckIns. + */ + cursor?: Prisma.CheckInWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CheckIns from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CheckIns. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of CheckIns. + */ + distinct?: Prisma.CheckInScalarFieldEnum | Prisma.CheckInScalarFieldEnum[] +} + +/** + * CheckIn findFirstOrThrow + */ +export type CheckInFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the CheckIn + */ + select?: Prisma.CheckInSelect | null + /** + * Omit specific fields from the CheckIn + */ + omit?: Prisma.CheckInOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckInInclude | null + /** + * Filter, which CheckIn to fetch. + */ + where?: Prisma.CheckInWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CheckIns to fetch. + */ + orderBy?: Prisma.CheckInOrderByWithRelationInput | Prisma.CheckInOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for CheckIns. + */ + cursor?: Prisma.CheckInWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CheckIns from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CheckIns. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of CheckIns. + */ + distinct?: Prisma.CheckInScalarFieldEnum | Prisma.CheckInScalarFieldEnum[] +} + +/** + * CheckIn findMany + */ +export type CheckInFindManyArgs = { + /** + * Select specific fields to fetch from the CheckIn + */ + select?: Prisma.CheckInSelect | null + /** + * Omit specific fields from the CheckIn + */ + omit?: Prisma.CheckInOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckInInclude | null + /** + * Filter, which CheckIns to fetch. + */ + where?: Prisma.CheckInWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CheckIns to fetch. + */ + orderBy?: Prisma.CheckInOrderByWithRelationInput | Prisma.CheckInOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing CheckIns. + */ + cursor?: Prisma.CheckInWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CheckIns from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CheckIns. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of CheckIns. + */ + distinct?: Prisma.CheckInScalarFieldEnum | Prisma.CheckInScalarFieldEnum[] +} + +/** + * CheckIn create + */ +export type CheckInCreateArgs = { + /** + * Select specific fields to fetch from the CheckIn + */ + select?: Prisma.CheckInSelect | null + /** + * Omit specific fields from the CheckIn + */ + omit?: Prisma.CheckInOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckInInclude | null + /** + * The data needed to create a CheckIn. + */ + data: Prisma.XOR +} + +/** + * CheckIn createMany + */ +export type CheckInCreateManyArgs = { + /** + * The data used to create many CheckIns. + */ + data: Prisma.CheckInCreateManyInput | Prisma.CheckInCreateManyInput[] + skipDuplicates?: boolean +} + +/** + * CheckIn createManyAndReturn + */ +export type CheckInCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the CheckIn + */ + select?: Prisma.CheckInSelectCreateManyAndReturn | null + /** + * Omit specific fields from the CheckIn + */ + omit?: Prisma.CheckInOmit | null + /** + * The data used to create many CheckIns. + */ + data: Prisma.CheckInCreateManyInput | Prisma.CheckInCreateManyInput[] + skipDuplicates?: boolean + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckInIncludeCreateManyAndReturn | null +} + +/** + * CheckIn update + */ +export type CheckInUpdateArgs = { + /** + * Select specific fields to fetch from the CheckIn + */ + select?: Prisma.CheckInSelect | null + /** + * Omit specific fields from the CheckIn + */ + omit?: Prisma.CheckInOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckInInclude | null + /** + * The data needed to update a CheckIn. + */ + data: Prisma.XOR + /** + * Choose, which CheckIn to update. + */ + where: Prisma.CheckInWhereUniqueInput +} + +/** + * CheckIn updateMany + */ +export type CheckInUpdateManyArgs = { + /** + * The data used to update CheckIns. + */ + data: Prisma.XOR + /** + * Filter which CheckIns to update + */ + where?: Prisma.CheckInWhereInput + /** + * Limit how many CheckIns to update. + */ + limit?: number +} + +/** + * CheckIn updateManyAndReturn + */ +export type CheckInUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the CheckIn + */ + select?: Prisma.CheckInSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the CheckIn + */ + omit?: Prisma.CheckInOmit | null + /** + * The data used to update CheckIns. + */ + data: Prisma.XOR + /** + * Filter which CheckIns to update + */ + where?: Prisma.CheckInWhereInput + /** + * Limit how many CheckIns to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckInIncludeUpdateManyAndReturn | null +} + +/** + * CheckIn upsert + */ +export type CheckInUpsertArgs = { + /** + * Select specific fields to fetch from the CheckIn + */ + select?: Prisma.CheckInSelect | null + /** + * Omit specific fields from the CheckIn + */ + omit?: Prisma.CheckInOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckInInclude | null + /** + * The filter to search for the CheckIn to update in case it exists. + */ + where: Prisma.CheckInWhereUniqueInput + /** + * In case the CheckIn found by the `where` argument doesn't exist, create a new CheckIn with this data. + */ + create: Prisma.XOR + /** + * In case the CheckIn was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * CheckIn delete + */ +export type CheckInDeleteArgs = { + /** + * Select specific fields to fetch from the CheckIn + */ + select?: Prisma.CheckInSelect | null + /** + * Omit specific fields from the CheckIn + */ + omit?: Prisma.CheckInOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckInInclude | null + /** + * Filter which CheckIn to delete. + */ + where: Prisma.CheckInWhereUniqueInput +} + +/** + * CheckIn deleteMany + */ +export type CheckInDeleteManyArgs = { + /** + * Filter which CheckIns to delete + */ + where?: Prisma.CheckInWhereInput + /** + * Limit how many CheckIns to delete. + */ + limit?: number +} + +/** + * CheckIn without action + */ +export type CheckInDefaultArgs = { + /** + * Select specific fields to fetch from the CheckIn + */ + select?: Prisma.CheckInSelect | null + /** + * Omit specific fields from the CheckIn + */ + omit?: Prisma.CheckInOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckInInclude | null +} diff --git a/generated/prisma/models/CheckList.ts b/generated/prisma/models/CheckList.ts new file mode 100644 index 0000000..d3b4ea5 --- /dev/null +++ b/generated/prisma/models/CheckList.ts @@ -0,0 +1,1642 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `CheckList` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums.ts" +import type * as Prisma from "../internal/prismaNamespace.ts" + +/** + * Model CheckList + * + */ +export type CheckListModel = runtime.Types.Result.DefaultSelection + +export type AggregateCheckList = { + _count: CheckListCountAggregateOutputType | null + _avg: CheckListAvgAggregateOutputType | null + _sum: CheckListSumAggregateOutputType | null + _min: CheckListMinAggregateOutputType | null + _max: CheckListMaxAggregateOutputType | null +} + +export type CheckListAvgAggregateOutputType = { + id: number | null + userId: number | null +} + +export type CheckListSumAggregateOutputType = { + id: number | null + userId: number | null +} + +export type CheckListMinAggregateOutputType = { + id: number | null + userId: number | null + title: string | null + scene: string | null + note: string | null + archived: boolean | null + createdAt: Date | null + updatedAt: Date | null +} + +export type CheckListMaxAggregateOutputType = { + id: number | null + userId: number | null + title: string | null + scene: string | null + note: string | null + archived: boolean | null + createdAt: Date | null + updatedAt: Date | null +} + +export type CheckListCountAggregateOutputType = { + id: number + userId: number + title: number + scene: number + note: number + archived: number + createdAt: number + updatedAt: number + _all: number +} + + +export type CheckListAvgAggregateInputType = { + id?: true + userId?: true +} + +export type CheckListSumAggregateInputType = { + id?: true + userId?: true +} + +export type CheckListMinAggregateInputType = { + id?: true + userId?: true + title?: true + scene?: true + note?: true + archived?: true + createdAt?: true + updatedAt?: true +} + +export type CheckListMaxAggregateInputType = { + id?: true + userId?: true + title?: true + scene?: true + note?: true + archived?: true + createdAt?: true + updatedAt?: true +} + +export type CheckListCountAggregateInputType = { + id?: true + userId?: true + title?: true + scene?: true + note?: true + archived?: true + createdAt?: true + updatedAt?: true + _all?: true +} + +export type CheckListAggregateArgs = { + /** + * Filter which CheckList to aggregate. + */ + where?: Prisma.CheckListWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CheckLists to fetch. + */ + orderBy?: Prisma.CheckListOrderByWithRelationInput | Prisma.CheckListOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.CheckListWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CheckLists from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CheckLists. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned CheckLists + **/ + _count?: true | CheckListCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to average + **/ + _avg?: CheckListAvgAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to sum + **/ + _sum?: CheckListSumAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: CheckListMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: CheckListMaxAggregateInputType +} + +export type GetCheckListAggregateType = { + [P in keyof T & keyof AggregateCheckList]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type CheckListGroupByArgs = { + where?: Prisma.CheckListWhereInput + orderBy?: Prisma.CheckListOrderByWithAggregationInput | Prisma.CheckListOrderByWithAggregationInput[] + by: Prisma.CheckListScalarFieldEnum[] | Prisma.CheckListScalarFieldEnum + having?: Prisma.CheckListScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: CheckListCountAggregateInputType | true + _avg?: CheckListAvgAggregateInputType + _sum?: CheckListSumAggregateInputType + _min?: CheckListMinAggregateInputType + _max?: CheckListMaxAggregateInputType +} + +export type CheckListGroupByOutputType = { + id: number + userId: number + title: string + scene: string + note: string + archived: boolean + createdAt: Date + updatedAt: Date + _count: CheckListCountAggregateOutputType | null + _avg: CheckListAvgAggregateOutputType | null + _sum: CheckListSumAggregateOutputType | null + _min: CheckListMinAggregateOutputType | null + _max: CheckListMaxAggregateOutputType | null +} + +export type GetCheckListGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof CheckListGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type CheckListWhereInput = { + AND?: Prisma.CheckListWhereInput | Prisma.CheckListWhereInput[] + OR?: Prisma.CheckListWhereInput[] + NOT?: Prisma.CheckListWhereInput | Prisma.CheckListWhereInput[] + id?: Prisma.IntFilter<"CheckList"> | number + userId?: Prisma.IntFilter<"CheckList"> | number + title?: Prisma.StringFilter<"CheckList"> | string + scene?: Prisma.StringFilter<"CheckList"> | string + note?: Prisma.StringFilter<"CheckList"> | string + archived?: Prisma.BoolFilter<"CheckList"> | boolean + createdAt?: Prisma.DateTimeFilter<"CheckList"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"CheckList"> | Date | string + user?: Prisma.XOR + items?: Prisma.CheckListItemListRelationFilter +} + +export type CheckListOrderByWithRelationInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrder + title?: Prisma.SortOrder + scene?: Prisma.SortOrder + note?: Prisma.SortOrder + archived?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + user?: Prisma.UserOrderByWithRelationInput + items?: Prisma.CheckListItemOrderByRelationAggregateInput +} + +export type CheckListWhereUniqueInput = Prisma.AtLeast<{ + id?: number + AND?: Prisma.CheckListWhereInput | Prisma.CheckListWhereInput[] + OR?: Prisma.CheckListWhereInput[] + NOT?: Prisma.CheckListWhereInput | Prisma.CheckListWhereInput[] + userId?: Prisma.IntFilter<"CheckList"> | number + title?: Prisma.StringFilter<"CheckList"> | string + scene?: Prisma.StringFilter<"CheckList"> | string + note?: Prisma.StringFilter<"CheckList"> | string + archived?: Prisma.BoolFilter<"CheckList"> | boolean + createdAt?: Prisma.DateTimeFilter<"CheckList"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"CheckList"> | Date | string + user?: Prisma.XOR + items?: Prisma.CheckListItemListRelationFilter +}, "id"> + +export type CheckListOrderByWithAggregationInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrder + title?: Prisma.SortOrder + scene?: Prisma.SortOrder + note?: Prisma.SortOrder + archived?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + _count?: Prisma.CheckListCountOrderByAggregateInput + _avg?: Prisma.CheckListAvgOrderByAggregateInput + _max?: Prisma.CheckListMaxOrderByAggregateInput + _min?: Prisma.CheckListMinOrderByAggregateInput + _sum?: Prisma.CheckListSumOrderByAggregateInput +} + +export type CheckListScalarWhereWithAggregatesInput = { + AND?: Prisma.CheckListScalarWhereWithAggregatesInput | Prisma.CheckListScalarWhereWithAggregatesInput[] + OR?: Prisma.CheckListScalarWhereWithAggregatesInput[] + NOT?: Prisma.CheckListScalarWhereWithAggregatesInput | Prisma.CheckListScalarWhereWithAggregatesInput[] + id?: Prisma.IntWithAggregatesFilter<"CheckList"> | number + userId?: Prisma.IntWithAggregatesFilter<"CheckList"> | number + title?: Prisma.StringWithAggregatesFilter<"CheckList"> | string + scene?: Prisma.StringWithAggregatesFilter<"CheckList"> | string + note?: Prisma.StringWithAggregatesFilter<"CheckList"> | string + archived?: Prisma.BoolWithAggregatesFilter<"CheckList"> | boolean + createdAt?: Prisma.DateTimeWithAggregatesFilter<"CheckList"> | Date | string + updatedAt?: Prisma.DateTimeWithAggregatesFilter<"CheckList"> | Date | string +} + +export type CheckListCreateInput = { + title: string + scene?: string + note?: string + archived?: boolean + createdAt?: Date | string + updatedAt?: Date | string + user: Prisma.UserCreateNestedOneWithoutCheckListsInput + items?: Prisma.CheckListItemCreateNestedManyWithoutListInput +} + +export type CheckListUncheckedCreateInput = { + id?: number + userId: number + title: string + scene?: string + note?: string + archived?: boolean + createdAt?: Date | string + updatedAt?: Date | string + items?: Prisma.CheckListItemUncheckedCreateNestedManyWithoutListInput +} + +export type CheckListUpdateInput = { + title?: Prisma.StringFieldUpdateOperationsInput | string + scene?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + user?: Prisma.UserUpdateOneRequiredWithoutCheckListsNestedInput + items?: Prisma.CheckListItemUpdateManyWithoutListNestedInput +} + +export type CheckListUncheckedUpdateInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.IntFieldUpdateOperationsInput | number + title?: Prisma.StringFieldUpdateOperationsInput | string + scene?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + items?: Prisma.CheckListItemUncheckedUpdateManyWithoutListNestedInput +} + +export type CheckListCreateManyInput = { + id?: number + userId: number + title: string + scene?: string + note?: string + archived?: boolean + createdAt?: Date | string + updatedAt?: Date | string +} + +export type CheckListUpdateManyMutationInput = { + title?: Prisma.StringFieldUpdateOperationsInput | string + scene?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CheckListUncheckedUpdateManyInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.IntFieldUpdateOperationsInput | number + title?: Prisma.StringFieldUpdateOperationsInput | string + scene?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CheckListListRelationFilter = { + every?: Prisma.CheckListWhereInput + some?: Prisma.CheckListWhereInput + none?: Prisma.CheckListWhereInput +} + +export type CheckListOrderByRelationAggregateInput = { + _count?: Prisma.SortOrder +} + +export type CheckListCountOrderByAggregateInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrder + title?: Prisma.SortOrder + scene?: Prisma.SortOrder + note?: Prisma.SortOrder + archived?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type CheckListAvgOrderByAggregateInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrder +} + +export type CheckListMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrder + title?: Prisma.SortOrder + scene?: Prisma.SortOrder + note?: Prisma.SortOrder + archived?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type CheckListMinOrderByAggregateInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrder + title?: Prisma.SortOrder + scene?: Prisma.SortOrder + note?: Prisma.SortOrder + archived?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type CheckListSumOrderByAggregateInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrder +} + +export type CheckListScalarRelationFilter = { + is?: Prisma.CheckListWhereInput + isNot?: Prisma.CheckListWhereInput +} + +export type CheckListCreateNestedManyWithoutUserInput = { + create?: Prisma.XOR | Prisma.CheckListCreateWithoutUserInput[] | Prisma.CheckListUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.CheckListCreateOrConnectWithoutUserInput | Prisma.CheckListCreateOrConnectWithoutUserInput[] + createMany?: Prisma.CheckListCreateManyUserInputEnvelope + connect?: Prisma.CheckListWhereUniqueInput | Prisma.CheckListWhereUniqueInput[] +} + +export type CheckListUncheckedCreateNestedManyWithoutUserInput = { + create?: Prisma.XOR | Prisma.CheckListCreateWithoutUserInput[] | Prisma.CheckListUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.CheckListCreateOrConnectWithoutUserInput | Prisma.CheckListCreateOrConnectWithoutUserInput[] + createMany?: Prisma.CheckListCreateManyUserInputEnvelope + connect?: Prisma.CheckListWhereUniqueInput | Prisma.CheckListWhereUniqueInput[] +} + +export type CheckListUpdateManyWithoutUserNestedInput = { + create?: Prisma.XOR | Prisma.CheckListCreateWithoutUserInput[] | Prisma.CheckListUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.CheckListCreateOrConnectWithoutUserInput | Prisma.CheckListCreateOrConnectWithoutUserInput[] + upsert?: Prisma.CheckListUpsertWithWhereUniqueWithoutUserInput | Prisma.CheckListUpsertWithWhereUniqueWithoutUserInput[] + createMany?: Prisma.CheckListCreateManyUserInputEnvelope + set?: Prisma.CheckListWhereUniqueInput | Prisma.CheckListWhereUniqueInput[] + disconnect?: Prisma.CheckListWhereUniqueInput | Prisma.CheckListWhereUniqueInput[] + delete?: Prisma.CheckListWhereUniqueInput | Prisma.CheckListWhereUniqueInput[] + connect?: Prisma.CheckListWhereUniqueInput | Prisma.CheckListWhereUniqueInput[] + update?: Prisma.CheckListUpdateWithWhereUniqueWithoutUserInput | Prisma.CheckListUpdateWithWhereUniqueWithoutUserInput[] + updateMany?: Prisma.CheckListUpdateManyWithWhereWithoutUserInput | Prisma.CheckListUpdateManyWithWhereWithoutUserInput[] + deleteMany?: Prisma.CheckListScalarWhereInput | Prisma.CheckListScalarWhereInput[] +} + +export type CheckListUncheckedUpdateManyWithoutUserNestedInput = { + create?: Prisma.XOR | Prisma.CheckListCreateWithoutUserInput[] | Prisma.CheckListUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.CheckListCreateOrConnectWithoutUserInput | Prisma.CheckListCreateOrConnectWithoutUserInput[] + upsert?: Prisma.CheckListUpsertWithWhereUniqueWithoutUserInput | Prisma.CheckListUpsertWithWhereUniqueWithoutUserInput[] + createMany?: Prisma.CheckListCreateManyUserInputEnvelope + set?: Prisma.CheckListWhereUniqueInput | Prisma.CheckListWhereUniqueInput[] + disconnect?: Prisma.CheckListWhereUniqueInput | Prisma.CheckListWhereUniqueInput[] + delete?: Prisma.CheckListWhereUniqueInput | Prisma.CheckListWhereUniqueInput[] + connect?: Prisma.CheckListWhereUniqueInput | Prisma.CheckListWhereUniqueInput[] + update?: Prisma.CheckListUpdateWithWhereUniqueWithoutUserInput | Prisma.CheckListUpdateWithWhereUniqueWithoutUserInput[] + updateMany?: Prisma.CheckListUpdateManyWithWhereWithoutUserInput | Prisma.CheckListUpdateManyWithWhereWithoutUserInput[] + deleteMany?: Prisma.CheckListScalarWhereInput | Prisma.CheckListScalarWhereInput[] +} + +export type CheckListCreateNestedOneWithoutItemsInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.CheckListCreateOrConnectWithoutItemsInput + connect?: Prisma.CheckListWhereUniqueInput +} + +export type CheckListUpdateOneRequiredWithoutItemsNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.CheckListCreateOrConnectWithoutItemsInput + upsert?: Prisma.CheckListUpsertWithoutItemsInput + connect?: Prisma.CheckListWhereUniqueInput + update?: Prisma.XOR, Prisma.CheckListUncheckedUpdateWithoutItemsInput> +} + +export type CheckListCreateWithoutUserInput = { + title: string + scene?: string + note?: string + archived?: boolean + createdAt?: Date | string + updatedAt?: Date | string + items?: Prisma.CheckListItemCreateNestedManyWithoutListInput +} + +export type CheckListUncheckedCreateWithoutUserInput = { + id?: number + title: string + scene?: string + note?: string + archived?: boolean + createdAt?: Date | string + updatedAt?: Date | string + items?: Prisma.CheckListItemUncheckedCreateNestedManyWithoutListInput +} + +export type CheckListCreateOrConnectWithoutUserInput = { + where: Prisma.CheckListWhereUniqueInput + create: Prisma.XOR +} + +export type CheckListCreateManyUserInputEnvelope = { + data: Prisma.CheckListCreateManyUserInput | Prisma.CheckListCreateManyUserInput[] + skipDuplicates?: boolean +} + +export type CheckListUpsertWithWhereUniqueWithoutUserInput = { + where: Prisma.CheckListWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type CheckListUpdateWithWhereUniqueWithoutUserInput = { + where: Prisma.CheckListWhereUniqueInput + data: Prisma.XOR +} + +export type CheckListUpdateManyWithWhereWithoutUserInput = { + where: Prisma.CheckListScalarWhereInput + data: Prisma.XOR +} + +export type CheckListScalarWhereInput = { + AND?: Prisma.CheckListScalarWhereInput | Prisma.CheckListScalarWhereInput[] + OR?: Prisma.CheckListScalarWhereInput[] + NOT?: Prisma.CheckListScalarWhereInput | Prisma.CheckListScalarWhereInput[] + id?: Prisma.IntFilter<"CheckList"> | number + userId?: Prisma.IntFilter<"CheckList"> | number + title?: Prisma.StringFilter<"CheckList"> | string + scene?: Prisma.StringFilter<"CheckList"> | string + note?: Prisma.StringFilter<"CheckList"> | string + archived?: Prisma.BoolFilter<"CheckList"> | boolean + createdAt?: Prisma.DateTimeFilter<"CheckList"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"CheckList"> | Date | string +} + +export type CheckListCreateWithoutItemsInput = { + title: string + scene?: string + note?: string + archived?: boolean + createdAt?: Date | string + updatedAt?: Date | string + user: Prisma.UserCreateNestedOneWithoutCheckListsInput +} + +export type CheckListUncheckedCreateWithoutItemsInput = { + id?: number + userId: number + title: string + scene?: string + note?: string + archived?: boolean + createdAt?: Date | string + updatedAt?: Date | string +} + +export type CheckListCreateOrConnectWithoutItemsInput = { + where: Prisma.CheckListWhereUniqueInput + create: Prisma.XOR +} + +export type CheckListUpsertWithoutItemsInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.CheckListWhereInput +} + +export type CheckListUpdateToOneWithWhereWithoutItemsInput = { + where?: Prisma.CheckListWhereInput + data: Prisma.XOR +} + +export type CheckListUpdateWithoutItemsInput = { + title?: Prisma.StringFieldUpdateOperationsInput | string + scene?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + user?: Prisma.UserUpdateOneRequiredWithoutCheckListsNestedInput +} + +export type CheckListUncheckedUpdateWithoutItemsInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.IntFieldUpdateOperationsInput | number + title?: Prisma.StringFieldUpdateOperationsInput | string + scene?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CheckListCreateManyUserInput = { + id?: number + title: string + scene?: string + note?: string + archived?: boolean + createdAt?: Date | string + updatedAt?: Date | string +} + +export type CheckListUpdateWithoutUserInput = { + title?: Prisma.StringFieldUpdateOperationsInput | string + scene?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + items?: Prisma.CheckListItemUpdateManyWithoutListNestedInput +} + +export type CheckListUncheckedUpdateWithoutUserInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + title?: Prisma.StringFieldUpdateOperationsInput | string + scene?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + items?: Prisma.CheckListItemUncheckedUpdateManyWithoutListNestedInput +} + +export type CheckListUncheckedUpdateManyWithoutUserInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + title?: Prisma.StringFieldUpdateOperationsInput | string + scene?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + + +/** + * Count Type CheckListCountOutputType + */ + +export type CheckListCountOutputType = { + items: number +} + +export type CheckListCountOutputTypeSelect = { + items?: boolean | CheckListCountOutputTypeCountItemsArgs +} + +/** + * CheckListCountOutputType without action + */ +export type CheckListCountOutputTypeDefaultArgs = { + /** + * Select specific fields to fetch from the CheckListCountOutputType + */ + select?: Prisma.CheckListCountOutputTypeSelect | null +} + +/** + * CheckListCountOutputType without action + */ +export type CheckListCountOutputTypeCountItemsArgs = { + where?: Prisma.CheckListItemWhereInput +} + + +export type CheckListSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + userId?: boolean + title?: boolean + scene?: boolean + note?: boolean + archived?: boolean + createdAt?: boolean + updatedAt?: boolean + user?: boolean | Prisma.UserDefaultArgs + items?: boolean | Prisma.CheckList$itemsArgs + _count?: boolean | Prisma.CheckListCountOutputTypeDefaultArgs +}, ExtArgs["result"]["checkList"]> + +export type CheckListSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + userId?: boolean + title?: boolean + scene?: boolean + note?: boolean + archived?: boolean + createdAt?: boolean + updatedAt?: boolean + user?: boolean | Prisma.UserDefaultArgs +}, ExtArgs["result"]["checkList"]> + +export type CheckListSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + userId?: boolean + title?: boolean + scene?: boolean + note?: boolean + archived?: boolean + createdAt?: boolean + updatedAt?: boolean + user?: boolean | Prisma.UserDefaultArgs +}, ExtArgs["result"]["checkList"]> + +export type CheckListSelectScalar = { + id?: boolean + userId?: boolean + title?: boolean + scene?: boolean + note?: boolean + archived?: boolean + createdAt?: boolean + updatedAt?: boolean +} + +export type CheckListOmit = runtime.Types.Extensions.GetOmit<"id" | "userId" | "title" | "scene" | "note" | "archived" | "createdAt" | "updatedAt", ExtArgs["result"]["checkList"]> +export type CheckListInclude = { + user?: boolean | Prisma.UserDefaultArgs + items?: boolean | Prisma.CheckList$itemsArgs + _count?: boolean | Prisma.CheckListCountOutputTypeDefaultArgs +} +export type CheckListIncludeCreateManyAndReturn = { + user?: boolean | Prisma.UserDefaultArgs +} +export type CheckListIncludeUpdateManyAndReturn = { + user?: boolean | Prisma.UserDefaultArgs +} + +export type $CheckListPayload = { + name: "CheckList" + objects: { + user: Prisma.$UserPayload + items: Prisma.$CheckListItemPayload[] + } + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: number + userId: number + title: string + scene: string + note: string + archived: boolean + createdAt: Date + updatedAt: Date + }, ExtArgs["result"]["checkList"]> + composites: {} +} + +export type CheckListGetPayload = runtime.Types.Result.GetResult + +export type CheckListCountArgs = + Omit & { + select?: CheckListCountAggregateInputType | true + } + +export interface CheckListDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['CheckList'], meta: { name: 'CheckList' } } + /** + * Find zero or one CheckList that matches the filter. + * @param {CheckListFindUniqueArgs} args - Arguments to find a CheckList + * @example + * // Get one CheckList + * const checkList = await prisma.checkList.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__CheckListClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one CheckList that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {CheckListFindUniqueOrThrowArgs} args - Arguments to find a CheckList + * @example + * // Get one CheckList + * const checkList = await prisma.checkList.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__CheckListClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first CheckList that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListFindFirstArgs} args - Arguments to find a CheckList + * @example + * // Get one CheckList + * const checkList = await prisma.checkList.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__CheckListClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first CheckList that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListFindFirstOrThrowArgs} args - Arguments to find a CheckList + * @example + * // Get one CheckList + * const checkList = await prisma.checkList.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__CheckListClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more CheckLists that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all CheckLists + * const checkLists = await prisma.checkList.findMany() + * + * // Get first 10 CheckLists + * const checkLists = await prisma.checkList.findMany({ take: 10 }) + * + * // Only select the `id` + * const checkListWithIdOnly = await prisma.checkList.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a CheckList. + * @param {CheckListCreateArgs} args - Arguments to create a CheckList. + * @example + * // Create one CheckList + * const CheckList = await prisma.checkList.create({ + * data: { + * // ... data to create a CheckList + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__CheckListClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many CheckLists. + * @param {CheckListCreateManyArgs} args - Arguments to create many CheckLists. + * @example + * // Create many CheckLists + * const checkList = await prisma.checkList.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many CheckLists and returns the data saved in the database. + * @param {CheckListCreateManyAndReturnArgs} args - Arguments to create many CheckLists. + * @example + * // Create many CheckLists + * const checkList = await prisma.checkList.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many CheckLists and only return the `id` + * const checkListWithIdOnly = await prisma.checkList.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a CheckList. + * @param {CheckListDeleteArgs} args - Arguments to delete one CheckList. + * @example + * // Delete one CheckList + * const CheckList = await prisma.checkList.delete({ + * where: { + * // ... filter to delete one CheckList + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__CheckListClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one CheckList. + * @param {CheckListUpdateArgs} args - Arguments to update one CheckList. + * @example + * // Update one CheckList + * const checkList = await prisma.checkList.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__CheckListClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more CheckLists. + * @param {CheckListDeleteManyArgs} args - Arguments to filter CheckLists to delete. + * @example + * // Delete a few CheckLists + * const { count } = await prisma.checkList.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more CheckLists. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many CheckLists + * const checkList = await prisma.checkList.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more CheckLists and returns the data updated in the database. + * @param {CheckListUpdateManyAndReturnArgs} args - Arguments to update many CheckLists. + * @example + * // Update many CheckLists + * const checkList = await prisma.checkList.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more CheckLists and only return the `id` + * const checkListWithIdOnly = await prisma.checkList.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one CheckList. + * @param {CheckListUpsertArgs} args - Arguments to update or create a CheckList. + * @example + * // Update or create a CheckList + * const checkList = await prisma.checkList.upsert({ + * create: { + * // ... data to create a CheckList + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the CheckList we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__CheckListClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of CheckLists. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListCountArgs} args - Arguments to filter CheckLists to count. + * @example + * // Count the number of CheckLists + * const count = await prisma.checkList.count({ + * where: { + * // ... the filter for the CheckLists we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a CheckList. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by CheckList. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends CheckListGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: CheckListGroupByArgs['orderBy'] } + : { orderBy?: CheckListGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetCheckListGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the CheckList model + */ +readonly fields: CheckListFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for CheckList. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__CheckListClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + user = {}>(args?: Prisma.Subset>): Prisma.Prisma__UserClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + items = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the CheckList model + */ +export interface CheckListFieldRefs { + readonly id: Prisma.FieldRef<"CheckList", 'Int'> + readonly userId: Prisma.FieldRef<"CheckList", 'Int'> + readonly title: Prisma.FieldRef<"CheckList", 'String'> + readonly scene: Prisma.FieldRef<"CheckList", 'String'> + readonly note: Prisma.FieldRef<"CheckList", 'String'> + readonly archived: Prisma.FieldRef<"CheckList", 'Boolean'> + readonly createdAt: Prisma.FieldRef<"CheckList", 'DateTime'> + readonly updatedAt: Prisma.FieldRef<"CheckList", 'DateTime'> +} + + +// Custom InputTypes +/** + * CheckList findUnique + */ +export type CheckListFindUniqueArgs = { + /** + * Select specific fields to fetch from the CheckList + */ + select?: Prisma.CheckListSelect | null + /** + * Omit specific fields from the CheckList + */ + omit?: Prisma.CheckListOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListInclude | null + /** + * Filter, which CheckList to fetch. + */ + where: Prisma.CheckListWhereUniqueInput +} + +/** + * CheckList findUniqueOrThrow + */ +export type CheckListFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the CheckList + */ + select?: Prisma.CheckListSelect | null + /** + * Omit specific fields from the CheckList + */ + omit?: Prisma.CheckListOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListInclude | null + /** + * Filter, which CheckList to fetch. + */ + where: Prisma.CheckListWhereUniqueInput +} + +/** + * CheckList findFirst + */ +export type CheckListFindFirstArgs = { + /** + * Select specific fields to fetch from the CheckList + */ + select?: Prisma.CheckListSelect | null + /** + * Omit specific fields from the CheckList + */ + omit?: Prisma.CheckListOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListInclude | null + /** + * Filter, which CheckList to fetch. + */ + where?: Prisma.CheckListWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CheckLists to fetch. + */ + orderBy?: Prisma.CheckListOrderByWithRelationInput | Prisma.CheckListOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for CheckLists. + */ + cursor?: Prisma.CheckListWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CheckLists from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CheckLists. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of CheckLists. + */ + distinct?: Prisma.CheckListScalarFieldEnum | Prisma.CheckListScalarFieldEnum[] +} + +/** + * CheckList findFirstOrThrow + */ +export type CheckListFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the CheckList + */ + select?: Prisma.CheckListSelect | null + /** + * Omit specific fields from the CheckList + */ + omit?: Prisma.CheckListOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListInclude | null + /** + * Filter, which CheckList to fetch. + */ + where?: Prisma.CheckListWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CheckLists to fetch. + */ + orderBy?: Prisma.CheckListOrderByWithRelationInput | Prisma.CheckListOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for CheckLists. + */ + cursor?: Prisma.CheckListWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CheckLists from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CheckLists. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of CheckLists. + */ + distinct?: Prisma.CheckListScalarFieldEnum | Prisma.CheckListScalarFieldEnum[] +} + +/** + * CheckList findMany + */ +export type CheckListFindManyArgs = { + /** + * Select specific fields to fetch from the CheckList + */ + select?: Prisma.CheckListSelect | null + /** + * Omit specific fields from the CheckList + */ + omit?: Prisma.CheckListOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListInclude | null + /** + * Filter, which CheckLists to fetch. + */ + where?: Prisma.CheckListWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CheckLists to fetch. + */ + orderBy?: Prisma.CheckListOrderByWithRelationInput | Prisma.CheckListOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing CheckLists. + */ + cursor?: Prisma.CheckListWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CheckLists from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CheckLists. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of CheckLists. + */ + distinct?: Prisma.CheckListScalarFieldEnum | Prisma.CheckListScalarFieldEnum[] +} + +/** + * CheckList create + */ +export type CheckListCreateArgs = { + /** + * Select specific fields to fetch from the CheckList + */ + select?: Prisma.CheckListSelect | null + /** + * Omit specific fields from the CheckList + */ + omit?: Prisma.CheckListOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListInclude | null + /** + * The data needed to create a CheckList. + */ + data: Prisma.XOR +} + +/** + * CheckList createMany + */ +export type CheckListCreateManyArgs = { + /** + * The data used to create many CheckLists. + */ + data: Prisma.CheckListCreateManyInput | Prisma.CheckListCreateManyInput[] + skipDuplicates?: boolean +} + +/** + * CheckList createManyAndReturn + */ +export type CheckListCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the CheckList + */ + select?: Prisma.CheckListSelectCreateManyAndReturn | null + /** + * Omit specific fields from the CheckList + */ + omit?: Prisma.CheckListOmit | null + /** + * The data used to create many CheckLists. + */ + data: Prisma.CheckListCreateManyInput | Prisma.CheckListCreateManyInput[] + skipDuplicates?: boolean + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListIncludeCreateManyAndReturn | null +} + +/** + * CheckList update + */ +export type CheckListUpdateArgs = { + /** + * Select specific fields to fetch from the CheckList + */ + select?: Prisma.CheckListSelect | null + /** + * Omit specific fields from the CheckList + */ + omit?: Prisma.CheckListOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListInclude | null + /** + * The data needed to update a CheckList. + */ + data: Prisma.XOR + /** + * Choose, which CheckList to update. + */ + where: Prisma.CheckListWhereUniqueInput +} + +/** + * CheckList updateMany + */ +export type CheckListUpdateManyArgs = { + /** + * The data used to update CheckLists. + */ + data: Prisma.XOR + /** + * Filter which CheckLists to update + */ + where?: Prisma.CheckListWhereInput + /** + * Limit how many CheckLists to update. + */ + limit?: number +} + +/** + * CheckList updateManyAndReturn + */ +export type CheckListUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the CheckList + */ + select?: Prisma.CheckListSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the CheckList + */ + omit?: Prisma.CheckListOmit | null + /** + * The data used to update CheckLists. + */ + data: Prisma.XOR + /** + * Filter which CheckLists to update + */ + where?: Prisma.CheckListWhereInput + /** + * Limit how many CheckLists to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListIncludeUpdateManyAndReturn | null +} + +/** + * CheckList upsert + */ +export type CheckListUpsertArgs = { + /** + * Select specific fields to fetch from the CheckList + */ + select?: Prisma.CheckListSelect | null + /** + * Omit specific fields from the CheckList + */ + omit?: Prisma.CheckListOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListInclude | null + /** + * The filter to search for the CheckList to update in case it exists. + */ + where: Prisma.CheckListWhereUniqueInput + /** + * In case the CheckList found by the `where` argument doesn't exist, create a new CheckList with this data. + */ + create: Prisma.XOR + /** + * In case the CheckList was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * CheckList delete + */ +export type CheckListDeleteArgs = { + /** + * Select specific fields to fetch from the CheckList + */ + select?: Prisma.CheckListSelect | null + /** + * Omit specific fields from the CheckList + */ + omit?: Prisma.CheckListOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListInclude | null + /** + * Filter which CheckList to delete. + */ + where: Prisma.CheckListWhereUniqueInput +} + +/** + * CheckList deleteMany + */ +export type CheckListDeleteManyArgs = { + /** + * Filter which CheckLists to delete + */ + where?: Prisma.CheckListWhereInput + /** + * Limit how many CheckLists to delete. + */ + limit?: number +} + +/** + * CheckList.items + */ +export type CheckList$itemsArgs = { + /** + * Select specific fields to fetch from the CheckListItem + */ + select?: Prisma.CheckListItemSelect | null + /** + * Omit specific fields from the CheckListItem + */ + omit?: Prisma.CheckListItemOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListItemInclude | null + where?: Prisma.CheckListItemWhereInput + orderBy?: Prisma.CheckListItemOrderByWithRelationInput | Prisma.CheckListItemOrderByWithRelationInput[] + cursor?: Prisma.CheckListItemWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.CheckListItemScalarFieldEnum | Prisma.CheckListItemScalarFieldEnum[] +} + +/** + * CheckList without action + */ +export type CheckListDefaultArgs = { + /** + * Select specific fields to fetch from the CheckList + */ + select?: Prisma.CheckListSelect | null + /** + * Omit specific fields from the CheckList + */ + omit?: Prisma.CheckListOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListInclude | null +} diff --git a/generated/prisma/models/CheckListItem.ts b/generated/prisma/models/CheckListItem.ts new file mode 100644 index 0000000..090f49f --- /dev/null +++ b/generated/prisma/models/CheckListItem.ts @@ -0,0 +1,1536 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `CheckListItem` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums.ts" +import type * as Prisma from "../internal/prismaNamespace.ts" + +/** + * Model CheckListItem + * + */ +export type CheckListItemModel = runtime.Types.Result.DefaultSelection + +export type AggregateCheckListItem = { + _count: CheckListItemCountAggregateOutputType | null + _avg: CheckListItemAvgAggregateOutputType | null + _sum: CheckListItemSumAggregateOutputType | null + _min: CheckListItemMinAggregateOutputType | null + _max: CheckListItemMaxAggregateOutputType | null +} + +export type CheckListItemAvgAggregateOutputType = { + id: number | null + listId: number | null + sortOrder: number | null +} + +export type CheckListItemSumAggregateOutputType = { + id: number | null + listId: number | null + sortOrder: number | null +} + +export type CheckListItemMinAggregateOutputType = { + id: number | null + listId: number | null + title: string | null + note: string | null + done: boolean | null + doneAt: Date | null + sortOrder: number | null + createdAt: Date | null + updatedAt: Date | null +} + +export type CheckListItemMaxAggregateOutputType = { + id: number | null + listId: number | null + title: string | null + note: string | null + done: boolean | null + doneAt: Date | null + sortOrder: number | null + createdAt: Date | null + updatedAt: Date | null +} + +export type CheckListItemCountAggregateOutputType = { + id: number + listId: number + title: number + note: number + done: number + doneAt: number + sortOrder: number + createdAt: number + updatedAt: number + _all: number +} + + +export type CheckListItemAvgAggregateInputType = { + id?: true + listId?: true + sortOrder?: true +} + +export type CheckListItemSumAggregateInputType = { + id?: true + listId?: true + sortOrder?: true +} + +export type CheckListItemMinAggregateInputType = { + id?: true + listId?: true + title?: true + note?: true + done?: true + doneAt?: true + sortOrder?: true + createdAt?: true + updatedAt?: true +} + +export type CheckListItemMaxAggregateInputType = { + id?: true + listId?: true + title?: true + note?: true + done?: true + doneAt?: true + sortOrder?: true + createdAt?: true + updatedAt?: true +} + +export type CheckListItemCountAggregateInputType = { + id?: true + listId?: true + title?: true + note?: true + done?: true + doneAt?: true + sortOrder?: true + createdAt?: true + updatedAt?: true + _all?: true +} + +export type CheckListItemAggregateArgs = { + /** + * Filter which CheckListItem to aggregate. + */ + where?: Prisma.CheckListItemWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CheckListItems to fetch. + */ + orderBy?: Prisma.CheckListItemOrderByWithRelationInput | Prisma.CheckListItemOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.CheckListItemWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CheckListItems from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CheckListItems. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned CheckListItems + **/ + _count?: true | CheckListItemCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to average + **/ + _avg?: CheckListItemAvgAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to sum + **/ + _sum?: CheckListItemSumAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: CheckListItemMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: CheckListItemMaxAggregateInputType +} + +export type GetCheckListItemAggregateType = { + [P in keyof T & keyof AggregateCheckListItem]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type CheckListItemGroupByArgs = { + where?: Prisma.CheckListItemWhereInput + orderBy?: Prisma.CheckListItemOrderByWithAggregationInput | Prisma.CheckListItemOrderByWithAggregationInput[] + by: Prisma.CheckListItemScalarFieldEnum[] | Prisma.CheckListItemScalarFieldEnum + having?: Prisma.CheckListItemScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: CheckListItemCountAggregateInputType | true + _avg?: CheckListItemAvgAggregateInputType + _sum?: CheckListItemSumAggregateInputType + _min?: CheckListItemMinAggregateInputType + _max?: CheckListItemMaxAggregateInputType +} + +export type CheckListItemGroupByOutputType = { + id: number + listId: number + title: string + note: string + done: boolean + doneAt: Date | null + sortOrder: number + createdAt: Date + updatedAt: Date + _count: CheckListItemCountAggregateOutputType | null + _avg: CheckListItemAvgAggregateOutputType | null + _sum: CheckListItemSumAggregateOutputType | null + _min: CheckListItemMinAggregateOutputType | null + _max: CheckListItemMaxAggregateOutputType | null +} + +export type GetCheckListItemGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof CheckListItemGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type CheckListItemWhereInput = { + AND?: Prisma.CheckListItemWhereInput | Prisma.CheckListItemWhereInput[] + OR?: Prisma.CheckListItemWhereInput[] + NOT?: Prisma.CheckListItemWhereInput | Prisma.CheckListItemWhereInput[] + id?: Prisma.IntFilter<"CheckListItem"> | number + listId?: Prisma.IntFilter<"CheckListItem"> | number + title?: Prisma.StringFilter<"CheckListItem"> | string + note?: Prisma.StringFilter<"CheckListItem"> | string + done?: Prisma.BoolFilter<"CheckListItem"> | boolean + doneAt?: Prisma.DateTimeNullableFilter<"CheckListItem"> | Date | string | null + sortOrder?: Prisma.IntFilter<"CheckListItem"> | number + createdAt?: Prisma.DateTimeFilter<"CheckListItem"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"CheckListItem"> | Date | string + list?: Prisma.XOR +} + +export type CheckListItemOrderByWithRelationInput = { + id?: Prisma.SortOrder + listId?: Prisma.SortOrder + title?: Prisma.SortOrder + note?: Prisma.SortOrder + done?: Prisma.SortOrder + doneAt?: Prisma.SortOrderInput | Prisma.SortOrder + sortOrder?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + list?: Prisma.CheckListOrderByWithRelationInput +} + +export type CheckListItemWhereUniqueInput = Prisma.AtLeast<{ + id?: number + AND?: Prisma.CheckListItemWhereInput | Prisma.CheckListItemWhereInput[] + OR?: Prisma.CheckListItemWhereInput[] + NOT?: Prisma.CheckListItemWhereInput | Prisma.CheckListItemWhereInput[] + listId?: Prisma.IntFilter<"CheckListItem"> | number + title?: Prisma.StringFilter<"CheckListItem"> | string + note?: Prisma.StringFilter<"CheckListItem"> | string + done?: Prisma.BoolFilter<"CheckListItem"> | boolean + doneAt?: Prisma.DateTimeNullableFilter<"CheckListItem"> | Date | string | null + sortOrder?: Prisma.IntFilter<"CheckListItem"> | number + createdAt?: Prisma.DateTimeFilter<"CheckListItem"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"CheckListItem"> | Date | string + list?: Prisma.XOR +}, "id"> + +export type CheckListItemOrderByWithAggregationInput = { + id?: Prisma.SortOrder + listId?: Prisma.SortOrder + title?: Prisma.SortOrder + note?: Prisma.SortOrder + done?: Prisma.SortOrder + doneAt?: Prisma.SortOrderInput | Prisma.SortOrder + sortOrder?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + _count?: Prisma.CheckListItemCountOrderByAggregateInput + _avg?: Prisma.CheckListItemAvgOrderByAggregateInput + _max?: Prisma.CheckListItemMaxOrderByAggregateInput + _min?: Prisma.CheckListItemMinOrderByAggregateInput + _sum?: Prisma.CheckListItemSumOrderByAggregateInput +} + +export type CheckListItemScalarWhereWithAggregatesInput = { + AND?: Prisma.CheckListItemScalarWhereWithAggregatesInput | Prisma.CheckListItemScalarWhereWithAggregatesInput[] + OR?: Prisma.CheckListItemScalarWhereWithAggregatesInput[] + NOT?: Prisma.CheckListItemScalarWhereWithAggregatesInput | Prisma.CheckListItemScalarWhereWithAggregatesInput[] + id?: Prisma.IntWithAggregatesFilter<"CheckListItem"> | number + listId?: Prisma.IntWithAggregatesFilter<"CheckListItem"> | number + title?: Prisma.StringWithAggregatesFilter<"CheckListItem"> | string + note?: Prisma.StringWithAggregatesFilter<"CheckListItem"> | string + done?: Prisma.BoolWithAggregatesFilter<"CheckListItem"> | boolean + doneAt?: Prisma.DateTimeNullableWithAggregatesFilter<"CheckListItem"> | Date | string | null + sortOrder?: Prisma.IntWithAggregatesFilter<"CheckListItem"> | number + createdAt?: Prisma.DateTimeWithAggregatesFilter<"CheckListItem"> | Date | string + updatedAt?: Prisma.DateTimeWithAggregatesFilter<"CheckListItem"> | Date | string +} + +export type CheckListItemCreateInput = { + title: string + note?: string + done?: boolean + doneAt?: Date | string | null + sortOrder?: number + createdAt?: Date | string + updatedAt?: Date | string + list: Prisma.CheckListCreateNestedOneWithoutItemsInput +} + +export type CheckListItemUncheckedCreateInput = { + id?: number + listId: number + title: string + note?: string + done?: boolean + doneAt?: Date | string | null + sortOrder?: number + createdAt?: Date | string + updatedAt?: Date | string +} + +export type CheckListItemUpdateInput = { + title?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + done?: Prisma.BoolFieldUpdateOperationsInput | boolean + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + list?: Prisma.CheckListUpdateOneRequiredWithoutItemsNestedInput +} + +export type CheckListItemUncheckedUpdateInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + listId?: Prisma.IntFieldUpdateOperationsInput | number + title?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + done?: Prisma.BoolFieldUpdateOperationsInput | boolean + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CheckListItemCreateManyInput = { + id?: number + listId: number + title: string + note?: string + done?: boolean + doneAt?: Date | string | null + sortOrder?: number + createdAt?: Date | string + updatedAt?: Date | string +} + +export type CheckListItemUpdateManyMutationInput = { + title?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + done?: Prisma.BoolFieldUpdateOperationsInput | boolean + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CheckListItemUncheckedUpdateManyInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + listId?: Prisma.IntFieldUpdateOperationsInput | number + title?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + done?: Prisma.BoolFieldUpdateOperationsInput | boolean + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CheckListItemListRelationFilter = { + every?: Prisma.CheckListItemWhereInput + some?: Prisma.CheckListItemWhereInput + none?: Prisma.CheckListItemWhereInput +} + +export type CheckListItemOrderByRelationAggregateInput = { + _count?: Prisma.SortOrder +} + +export type CheckListItemCountOrderByAggregateInput = { + id?: Prisma.SortOrder + listId?: Prisma.SortOrder + title?: Prisma.SortOrder + note?: Prisma.SortOrder + done?: Prisma.SortOrder + doneAt?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type CheckListItemAvgOrderByAggregateInput = { + id?: Prisma.SortOrder + listId?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder +} + +export type CheckListItemMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + listId?: Prisma.SortOrder + title?: Prisma.SortOrder + note?: Prisma.SortOrder + done?: Prisma.SortOrder + doneAt?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type CheckListItemMinOrderByAggregateInput = { + id?: Prisma.SortOrder + listId?: Prisma.SortOrder + title?: Prisma.SortOrder + note?: Prisma.SortOrder + done?: Prisma.SortOrder + doneAt?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type CheckListItemSumOrderByAggregateInput = { + id?: Prisma.SortOrder + listId?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder +} + +export type CheckListItemCreateNestedManyWithoutListInput = { + create?: Prisma.XOR | Prisma.CheckListItemCreateWithoutListInput[] | Prisma.CheckListItemUncheckedCreateWithoutListInput[] + connectOrCreate?: Prisma.CheckListItemCreateOrConnectWithoutListInput | Prisma.CheckListItemCreateOrConnectWithoutListInput[] + createMany?: Prisma.CheckListItemCreateManyListInputEnvelope + connect?: Prisma.CheckListItemWhereUniqueInput | Prisma.CheckListItemWhereUniqueInput[] +} + +export type CheckListItemUncheckedCreateNestedManyWithoutListInput = { + create?: Prisma.XOR | Prisma.CheckListItemCreateWithoutListInput[] | Prisma.CheckListItemUncheckedCreateWithoutListInput[] + connectOrCreate?: Prisma.CheckListItemCreateOrConnectWithoutListInput | Prisma.CheckListItemCreateOrConnectWithoutListInput[] + createMany?: Prisma.CheckListItemCreateManyListInputEnvelope + connect?: Prisma.CheckListItemWhereUniqueInput | Prisma.CheckListItemWhereUniqueInput[] +} + +export type CheckListItemUpdateManyWithoutListNestedInput = { + create?: Prisma.XOR | Prisma.CheckListItemCreateWithoutListInput[] | Prisma.CheckListItemUncheckedCreateWithoutListInput[] + connectOrCreate?: Prisma.CheckListItemCreateOrConnectWithoutListInput | Prisma.CheckListItemCreateOrConnectWithoutListInput[] + upsert?: Prisma.CheckListItemUpsertWithWhereUniqueWithoutListInput | Prisma.CheckListItemUpsertWithWhereUniqueWithoutListInput[] + createMany?: Prisma.CheckListItemCreateManyListInputEnvelope + set?: Prisma.CheckListItemWhereUniqueInput | Prisma.CheckListItemWhereUniqueInput[] + disconnect?: Prisma.CheckListItemWhereUniqueInput | Prisma.CheckListItemWhereUniqueInput[] + delete?: Prisma.CheckListItemWhereUniqueInput | Prisma.CheckListItemWhereUniqueInput[] + connect?: Prisma.CheckListItemWhereUniqueInput | Prisma.CheckListItemWhereUniqueInput[] + update?: Prisma.CheckListItemUpdateWithWhereUniqueWithoutListInput | Prisma.CheckListItemUpdateWithWhereUniqueWithoutListInput[] + updateMany?: Prisma.CheckListItemUpdateManyWithWhereWithoutListInput | Prisma.CheckListItemUpdateManyWithWhereWithoutListInput[] + deleteMany?: Prisma.CheckListItemScalarWhereInput | Prisma.CheckListItemScalarWhereInput[] +} + +export type CheckListItemUncheckedUpdateManyWithoutListNestedInput = { + create?: Prisma.XOR | Prisma.CheckListItemCreateWithoutListInput[] | Prisma.CheckListItemUncheckedCreateWithoutListInput[] + connectOrCreate?: Prisma.CheckListItemCreateOrConnectWithoutListInput | Prisma.CheckListItemCreateOrConnectWithoutListInput[] + upsert?: Prisma.CheckListItemUpsertWithWhereUniqueWithoutListInput | Prisma.CheckListItemUpsertWithWhereUniqueWithoutListInput[] + createMany?: Prisma.CheckListItemCreateManyListInputEnvelope + set?: Prisma.CheckListItemWhereUniqueInput | Prisma.CheckListItemWhereUniqueInput[] + disconnect?: Prisma.CheckListItemWhereUniqueInput | Prisma.CheckListItemWhereUniqueInput[] + delete?: Prisma.CheckListItemWhereUniqueInput | Prisma.CheckListItemWhereUniqueInput[] + connect?: Prisma.CheckListItemWhereUniqueInput | Prisma.CheckListItemWhereUniqueInput[] + update?: Prisma.CheckListItemUpdateWithWhereUniqueWithoutListInput | Prisma.CheckListItemUpdateWithWhereUniqueWithoutListInput[] + updateMany?: Prisma.CheckListItemUpdateManyWithWhereWithoutListInput | Prisma.CheckListItemUpdateManyWithWhereWithoutListInput[] + deleteMany?: Prisma.CheckListItemScalarWhereInput | Prisma.CheckListItemScalarWhereInput[] +} + +export type CheckListItemCreateWithoutListInput = { + title: string + note?: string + done?: boolean + doneAt?: Date | string | null + sortOrder?: number + createdAt?: Date | string + updatedAt?: Date | string +} + +export type CheckListItemUncheckedCreateWithoutListInput = { + id?: number + title: string + note?: string + done?: boolean + doneAt?: Date | string | null + sortOrder?: number + createdAt?: Date | string + updatedAt?: Date | string +} + +export type CheckListItemCreateOrConnectWithoutListInput = { + where: Prisma.CheckListItemWhereUniqueInput + create: Prisma.XOR +} + +export type CheckListItemCreateManyListInputEnvelope = { + data: Prisma.CheckListItemCreateManyListInput | Prisma.CheckListItemCreateManyListInput[] + skipDuplicates?: boolean +} + +export type CheckListItemUpsertWithWhereUniqueWithoutListInput = { + where: Prisma.CheckListItemWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type CheckListItemUpdateWithWhereUniqueWithoutListInput = { + where: Prisma.CheckListItemWhereUniqueInput + data: Prisma.XOR +} + +export type CheckListItemUpdateManyWithWhereWithoutListInput = { + where: Prisma.CheckListItemScalarWhereInput + data: Prisma.XOR +} + +export type CheckListItemScalarWhereInput = { + AND?: Prisma.CheckListItemScalarWhereInput | Prisma.CheckListItemScalarWhereInput[] + OR?: Prisma.CheckListItemScalarWhereInput[] + NOT?: Prisma.CheckListItemScalarWhereInput | Prisma.CheckListItemScalarWhereInput[] + id?: Prisma.IntFilter<"CheckListItem"> | number + listId?: Prisma.IntFilter<"CheckListItem"> | number + title?: Prisma.StringFilter<"CheckListItem"> | string + note?: Prisma.StringFilter<"CheckListItem"> | string + done?: Prisma.BoolFilter<"CheckListItem"> | boolean + doneAt?: Prisma.DateTimeNullableFilter<"CheckListItem"> | Date | string | null + sortOrder?: Prisma.IntFilter<"CheckListItem"> | number + createdAt?: Prisma.DateTimeFilter<"CheckListItem"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"CheckListItem"> | Date | string +} + +export type CheckListItemCreateManyListInput = { + id?: number + title: string + note?: string + done?: boolean + doneAt?: Date | string | null + sortOrder?: number + createdAt?: Date | string + updatedAt?: Date | string +} + +export type CheckListItemUpdateWithoutListInput = { + title?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + done?: Prisma.BoolFieldUpdateOperationsInput | boolean + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CheckListItemUncheckedUpdateWithoutListInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + title?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + done?: Prisma.BoolFieldUpdateOperationsInput | boolean + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CheckListItemUncheckedUpdateManyWithoutListInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + title?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + done?: Prisma.BoolFieldUpdateOperationsInput | boolean + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + + + +export type CheckListItemSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + listId?: boolean + title?: boolean + note?: boolean + done?: boolean + doneAt?: boolean + sortOrder?: boolean + createdAt?: boolean + updatedAt?: boolean + list?: boolean | Prisma.CheckListDefaultArgs +}, ExtArgs["result"]["checkListItem"]> + +export type CheckListItemSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + listId?: boolean + title?: boolean + note?: boolean + done?: boolean + doneAt?: boolean + sortOrder?: boolean + createdAt?: boolean + updatedAt?: boolean + list?: boolean | Prisma.CheckListDefaultArgs +}, ExtArgs["result"]["checkListItem"]> + +export type CheckListItemSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + listId?: boolean + title?: boolean + note?: boolean + done?: boolean + doneAt?: boolean + sortOrder?: boolean + createdAt?: boolean + updatedAt?: boolean + list?: boolean | Prisma.CheckListDefaultArgs +}, ExtArgs["result"]["checkListItem"]> + +export type CheckListItemSelectScalar = { + id?: boolean + listId?: boolean + title?: boolean + note?: boolean + done?: boolean + doneAt?: boolean + sortOrder?: boolean + createdAt?: boolean + updatedAt?: boolean +} + +export type CheckListItemOmit = runtime.Types.Extensions.GetOmit<"id" | "listId" | "title" | "note" | "done" | "doneAt" | "sortOrder" | "createdAt" | "updatedAt", ExtArgs["result"]["checkListItem"]> +export type CheckListItemInclude = { + list?: boolean | Prisma.CheckListDefaultArgs +} +export type CheckListItemIncludeCreateManyAndReturn = { + list?: boolean | Prisma.CheckListDefaultArgs +} +export type CheckListItemIncludeUpdateManyAndReturn = { + list?: boolean | Prisma.CheckListDefaultArgs +} + +export type $CheckListItemPayload = { + name: "CheckListItem" + objects: { + list: Prisma.$CheckListPayload + } + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: number + listId: number + title: string + note: string + done: boolean + doneAt: Date | null + sortOrder: number + createdAt: Date + updatedAt: Date + }, ExtArgs["result"]["checkListItem"]> + composites: {} +} + +export type CheckListItemGetPayload = runtime.Types.Result.GetResult + +export type CheckListItemCountArgs = + Omit & { + select?: CheckListItemCountAggregateInputType | true + } + +export interface CheckListItemDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['CheckListItem'], meta: { name: 'CheckListItem' } } + /** + * Find zero or one CheckListItem that matches the filter. + * @param {CheckListItemFindUniqueArgs} args - Arguments to find a CheckListItem + * @example + * // Get one CheckListItem + * const checkListItem = await prisma.checkListItem.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__CheckListItemClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one CheckListItem that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {CheckListItemFindUniqueOrThrowArgs} args - Arguments to find a CheckListItem + * @example + * // Get one CheckListItem + * const checkListItem = await prisma.checkListItem.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__CheckListItemClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first CheckListItem that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListItemFindFirstArgs} args - Arguments to find a CheckListItem + * @example + * // Get one CheckListItem + * const checkListItem = await prisma.checkListItem.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__CheckListItemClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first CheckListItem that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListItemFindFirstOrThrowArgs} args - Arguments to find a CheckListItem + * @example + * // Get one CheckListItem + * const checkListItem = await prisma.checkListItem.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__CheckListItemClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more CheckListItems that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListItemFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all CheckListItems + * const checkListItems = await prisma.checkListItem.findMany() + * + * // Get first 10 CheckListItems + * const checkListItems = await prisma.checkListItem.findMany({ take: 10 }) + * + * // Only select the `id` + * const checkListItemWithIdOnly = await prisma.checkListItem.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a CheckListItem. + * @param {CheckListItemCreateArgs} args - Arguments to create a CheckListItem. + * @example + * // Create one CheckListItem + * const CheckListItem = await prisma.checkListItem.create({ + * data: { + * // ... data to create a CheckListItem + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__CheckListItemClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many CheckListItems. + * @param {CheckListItemCreateManyArgs} args - Arguments to create many CheckListItems. + * @example + * // Create many CheckListItems + * const checkListItem = await prisma.checkListItem.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many CheckListItems and returns the data saved in the database. + * @param {CheckListItemCreateManyAndReturnArgs} args - Arguments to create many CheckListItems. + * @example + * // Create many CheckListItems + * const checkListItem = await prisma.checkListItem.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many CheckListItems and only return the `id` + * const checkListItemWithIdOnly = await prisma.checkListItem.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a CheckListItem. + * @param {CheckListItemDeleteArgs} args - Arguments to delete one CheckListItem. + * @example + * // Delete one CheckListItem + * const CheckListItem = await prisma.checkListItem.delete({ + * where: { + * // ... filter to delete one CheckListItem + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__CheckListItemClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one CheckListItem. + * @param {CheckListItemUpdateArgs} args - Arguments to update one CheckListItem. + * @example + * // Update one CheckListItem + * const checkListItem = await prisma.checkListItem.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__CheckListItemClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more CheckListItems. + * @param {CheckListItemDeleteManyArgs} args - Arguments to filter CheckListItems to delete. + * @example + * // Delete a few CheckListItems + * const { count } = await prisma.checkListItem.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more CheckListItems. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListItemUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many CheckListItems + * const checkListItem = await prisma.checkListItem.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more CheckListItems and returns the data updated in the database. + * @param {CheckListItemUpdateManyAndReturnArgs} args - Arguments to update many CheckListItems. + * @example + * // Update many CheckListItems + * const checkListItem = await prisma.checkListItem.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more CheckListItems and only return the `id` + * const checkListItemWithIdOnly = await prisma.checkListItem.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one CheckListItem. + * @param {CheckListItemUpsertArgs} args - Arguments to update or create a CheckListItem. + * @example + * // Update or create a CheckListItem + * const checkListItem = await prisma.checkListItem.upsert({ + * create: { + * // ... data to create a CheckListItem + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the CheckListItem we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__CheckListItemClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of CheckListItems. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListItemCountArgs} args - Arguments to filter CheckListItems to count. + * @example + * // Count the number of CheckListItems + * const count = await prisma.checkListItem.count({ + * where: { + * // ... the filter for the CheckListItems we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a CheckListItem. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListItemAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by CheckListItem. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListItemGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends CheckListItemGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: CheckListItemGroupByArgs['orderBy'] } + : { orderBy?: CheckListItemGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetCheckListItemGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the CheckListItem model + */ +readonly fields: CheckListItemFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for CheckListItem. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__CheckListItemClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + list = {}>(args?: Prisma.Subset>): Prisma.Prisma__CheckListClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the CheckListItem model + */ +export interface CheckListItemFieldRefs { + readonly id: Prisma.FieldRef<"CheckListItem", 'Int'> + readonly listId: Prisma.FieldRef<"CheckListItem", 'Int'> + readonly title: Prisma.FieldRef<"CheckListItem", 'String'> + readonly note: Prisma.FieldRef<"CheckListItem", 'String'> + readonly done: Prisma.FieldRef<"CheckListItem", 'Boolean'> + readonly doneAt: Prisma.FieldRef<"CheckListItem", 'DateTime'> + readonly sortOrder: Prisma.FieldRef<"CheckListItem", 'Int'> + readonly createdAt: Prisma.FieldRef<"CheckListItem", 'DateTime'> + readonly updatedAt: Prisma.FieldRef<"CheckListItem", 'DateTime'> +} + + +// Custom InputTypes +/** + * CheckListItem findUnique + */ +export type CheckListItemFindUniqueArgs = { + /** + * Select specific fields to fetch from the CheckListItem + */ + select?: Prisma.CheckListItemSelect | null + /** + * Omit specific fields from the CheckListItem + */ + omit?: Prisma.CheckListItemOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListItemInclude | null + /** + * Filter, which CheckListItem to fetch. + */ + where: Prisma.CheckListItemWhereUniqueInput +} + +/** + * CheckListItem findUniqueOrThrow + */ +export type CheckListItemFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the CheckListItem + */ + select?: Prisma.CheckListItemSelect | null + /** + * Omit specific fields from the CheckListItem + */ + omit?: Prisma.CheckListItemOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListItemInclude | null + /** + * Filter, which CheckListItem to fetch. + */ + where: Prisma.CheckListItemWhereUniqueInput +} + +/** + * CheckListItem findFirst + */ +export type CheckListItemFindFirstArgs = { + /** + * Select specific fields to fetch from the CheckListItem + */ + select?: Prisma.CheckListItemSelect | null + /** + * Omit specific fields from the CheckListItem + */ + omit?: Prisma.CheckListItemOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListItemInclude | null + /** + * Filter, which CheckListItem to fetch. + */ + where?: Prisma.CheckListItemWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CheckListItems to fetch. + */ + orderBy?: Prisma.CheckListItemOrderByWithRelationInput | Prisma.CheckListItemOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for CheckListItems. + */ + cursor?: Prisma.CheckListItemWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CheckListItems from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CheckListItems. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of CheckListItems. + */ + distinct?: Prisma.CheckListItemScalarFieldEnum | Prisma.CheckListItemScalarFieldEnum[] +} + +/** + * CheckListItem findFirstOrThrow + */ +export type CheckListItemFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the CheckListItem + */ + select?: Prisma.CheckListItemSelect | null + /** + * Omit specific fields from the CheckListItem + */ + omit?: Prisma.CheckListItemOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListItemInclude | null + /** + * Filter, which CheckListItem to fetch. + */ + where?: Prisma.CheckListItemWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CheckListItems to fetch. + */ + orderBy?: Prisma.CheckListItemOrderByWithRelationInput | Prisma.CheckListItemOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for CheckListItems. + */ + cursor?: Prisma.CheckListItemWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CheckListItems from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CheckListItems. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of CheckListItems. + */ + distinct?: Prisma.CheckListItemScalarFieldEnum | Prisma.CheckListItemScalarFieldEnum[] +} + +/** + * CheckListItem findMany + */ +export type CheckListItemFindManyArgs = { + /** + * Select specific fields to fetch from the CheckListItem + */ + select?: Prisma.CheckListItemSelect | null + /** + * Omit specific fields from the CheckListItem + */ + omit?: Prisma.CheckListItemOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListItemInclude | null + /** + * Filter, which CheckListItems to fetch. + */ + where?: Prisma.CheckListItemWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CheckListItems to fetch. + */ + orderBy?: Prisma.CheckListItemOrderByWithRelationInput | Prisma.CheckListItemOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing CheckListItems. + */ + cursor?: Prisma.CheckListItemWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CheckListItems from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CheckListItems. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of CheckListItems. + */ + distinct?: Prisma.CheckListItemScalarFieldEnum | Prisma.CheckListItemScalarFieldEnum[] +} + +/** + * CheckListItem create + */ +export type CheckListItemCreateArgs = { + /** + * Select specific fields to fetch from the CheckListItem + */ + select?: Prisma.CheckListItemSelect | null + /** + * Omit specific fields from the CheckListItem + */ + omit?: Prisma.CheckListItemOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListItemInclude | null + /** + * The data needed to create a CheckListItem. + */ + data: Prisma.XOR +} + +/** + * CheckListItem createMany + */ +export type CheckListItemCreateManyArgs = { + /** + * The data used to create many CheckListItems. + */ + data: Prisma.CheckListItemCreateManyInput | Prisma.CheckListItemCreateManyInput[] + skipDuplicates?: boolean +} + +/** + * CheckListItem createManyAndReturn + */ +export type CheckListItemCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the CheckListItem + */ + select?: Prisma.CheckListItemSelectCreateManyAndReturn | null + /** + * Omit specific fields from the CheckListItem + */ + omit?: Prisma.CheckListItemOmit | null + /** + * The data used to create many CheckListItems. + */ + data: Prisma.CheckListItemCreateManyInput | Prisma.CheckListItemCreateManyInput[] + skipDuplicates?: boolean + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListItemIncludeCreateManyAndReturn | null +} + +/** + * CheckListItem update + */ +export type CheckListItemUpdateArgs = { + /** + * Select specific fields to fetch from the CheckListItem + */ + select?: Prisma.CheckListItemSelect | null + /** + * Omit specific fields from the CheckListItem + */ + omit?: Prisma.CheckListItemOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListItemInclude | null + /** + * The data needed to update a CheckListItem. + */ + data: Prisma.XOR + /** + * Choose, which CheckListItem to update. + */ + where: Prisma.CheckListItemWhereUniqueInput +} + +/** + * CheckListItem updateMany + */ +export type CheckListItemUpdateManyArgs = { + /** + * The data used to update CheckListItems. + */ + data: Prisma.XOR + /** + * Filter which CheckListItems to update + */ + where?: Prisma.CheckListItemWhereInput + /** + * Limit how many CheckListItems to update. + */ + limit?: number +} + +/** + * CheckListItem updateManyAndReturn + */ +export type CheckListItemUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the CheckListItem + */ + select?: Prisma.CheckListItemSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the CheckListItem + */ + omit?: Prisma.CheckListItemOmit | null + /** + * The data used to update CheckListItems. + */ + data: Prisma.XOR + /** + * Filter which CheckListItems to update + */ + where?: Prisma.CheckListItemWhereInput + /** + * Limit how many CheckListItems to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListItemIncludeUpdateManyAndReturn | null +} + +/** + * CheckListItem upsert + */ +export type CheckListItemUpsertArgs = { + /** + * Select specific fields to fetch from the CheckListItem + */ + select?: Prisma.CheckListItemSelect | null + /** + * Omit specific fields from the CheckListItem + */ + omit?: Prisma.CheckListItemOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListItemInclude | null + /** + * The filter to search for the CheckListItem to update in case it exists. + */ + where: Prisma.CheckListItemWhereUniqueInput + /** + * In case the CheckListItem found by the `where` argument doesn't exist, create a new CheckListItem with this data. + */ + create: Prisma.XOR + /** + * In case the CheckListItem was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * CheckListItem delete + */ +export type CheckListItemDeleteArgs = { + /** + * Select specific fields to fetch from the CheckListItem + */ + select?: Prisma.CheckListItemSelect | null + /** + * Omit specific fields from the CheckListItem + */ + omit?: Prisma.CheckListItemOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListItemInclude | null + /** + * Filter which CheckListItem to delete. + */ + where: Prisma.CheckListItemWhereUniqueInput +} + +/** + * CheckListItem deleteMany + */ +export type CheckListItemDeleteManyArgs = { + /** + * Filter which CheckListItems to delete + */ + where?: Prisma.CheckListItemWhereInput + /** + * Limit how many CheckListItems to delete. + */ + limit?: number +} + +/** + * CheckListItem without action + */ +export type CheckListItemDefaultArgs = { + /** + * Select specific fields to fetch from the CheckListItem + */ + select?: Prisma.CheckListItemSelect | null + /** + * Omit specific fields from the CheckListItem + */ + omit?: Prisma.CheckListItemOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListItemInclude | null +} diff --git a/generated/prisma/models/CheckListTemplate.ts b/generated/prisma/models/CheckListTemplate.ts new file mode 100644 index 0000000..ce406ee --- /dev/null +++ b/generated/prisma/models/CheckListTemplate.ts @@ -0,0 +1,1508 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `CheckListTemplate` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums.ts" +import type * as Prisma from "../internal/prismaNamespace.ts" + +/** + * Model CheckListTemplate + * + */ +export type CheckListTemplateModel = runtime.Types.Result.DefaultSelection + +export type AggregateCheckListTemplate = { + _count: CheckListTemplateCountAggregateOutputType | null + _avg: CheckListTemplateAvgAggregateOutputType | null + _sum: CheckListTemplateSumAggregateOutputType | null + _min: CheckListTemplateMinAggregateOutputType | null + _max: CheckListTemplateMaxAggregateOutputType | null +} + +export type CheckListTemplateAvgAggregateOutputType = { + id: number | null + userId: number | null +} + +export type CheckListTemplateSumAggregateOutputType = { + id: number | null + userId: number | null +} + +export type CheckListTemplateMinAggregateOutputType = { + id: number | null + userId: number | null + name: string | null + scene: string | null + builtin: boolean | null + createdAt: Date | null + updatedAt: Date | null +} + +export type CheckListTemplateMaxAggregateOutputType = { + id: number | null + userId: number | null + name: string | null + scene: string | null + builtin: boolean | null + createdAt: Date | null + updatedAt: Date | null +} + +export type CheckListTemplateCountAggregateOutputType = { + id: number + userId: number + name: number + scene: number + items: number + builtin: number + createdAt: number + updatedAt: number + _all: number +} + + +export type CheckListTemplateAvgAggregateInputType = { + id?: true + userId?: true +} + +export type CheckListTemplateSumAggregateInputType = { + id?: true + userId?: true +} + +export type CheckListTemplateMinAggregateInputType = { + id?: true + userId?: true + name?: true + scene?: true + builtin?: true + createdAt?: true + updatedAt?: true +} + +export type CheckListTemplateMaxAggregateInputType = { + id?: true + userId?: true + name?: true + scene?: true + builtin?: true + createdAt?: true + updatedAt?: true +} + +export type CheckListTemplateCountAggregateInputType = { + id?: true + userId?: true + name?: true + scene?: true + items?: true + builtin?: true + createdAt?: true + updatedAt?: true + _all?: true +} + +export type CheckListTemplateAggregateArgs = { + /** + * Filter which CheckListTemplate to aggregate. + */ + where?: Prisma.CheckListTemplateWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CheckListTemplates to fetch. + */ + orderBy?: Prisma.CheckListTemplateOrderByWithRelationInput | Prisma.CheckListTemplateOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.CheckListTemplateWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CheckListTemplates from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CheckListTemplates. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned CheckListTemplates + **/ + _count?: true | CheckListTemplateCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to average + **/ + _avg?: CheckListTemplateAvgAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to sum + **/ + _sum?: CheckListTemplateSumAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: CheckListTemplateMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: CheckListTemplateMaxAggregateInputType +} + +export type GetCheckListTemplateAggregateType = { + [P in keyof T & keyof AggregateCheckListTemplate]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type CheckListTemplateGroupByArgs = { + where?: Prisma.CheckListTemplateWhereInput + orderBy?: Prisma.CheckListTemplateOrderByWithAggregationInput | Prisma.CheckListTemplateOrderByWithAggregationInput[] + by: Prisma.CheckListTemplateScalarFieldEnum[] | Prisma.CheckListTemplateScalarFieldEnum + having?: Prisma.CheckListTemplateScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: CheckListTemplateCountAggregateInputType | true + _avg?: CheckListTemplateAvgAggregateInputType + _sum?: CheckListTemplateSumAggregateInputType + _min?: CheckListTemplateMinAggregateInputType + _max?: CheckListTemplateMaxAggregateInputType +} + +export type CheckListTemplateGroupByOutputType = { + id: number + userId: number | null + name: string + scene: string + items: runtime.JsonValue + builtin: boolean + createdAt: Date + updatedAt: Date + _count: CheckListTemplateCountAggregateOutputType | null + _avg: CheckListTemplateAvgAggregateOutputType | null + _sum: CheckListTemplateSumAggregateOutputType | null + _min: CheckListTemplateMinAggregateOutputType | null + _max: CheckListTemplateMaxAggregateOutputType | null +} + +export type GetCheckListTemplateGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof CheckListTemplateGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type CheckListTemplateWhereInput = { + AND?: Prisma.CheckListTemplateWhereInput | Prisma.CheckListTemplateWhereInput[] + OR?: Prisma.CheckListTemplateWhereInput[] + NOT?: Prisma.CheckListTemplateWhereInput | Prisma.CheckListTemplateWhereInput[] + id?: Prisma.IntFilter<"CheckListTemplate"> | number + userId?: Prisma.IntNullableFilter<"CheckListTemplate"> | number | null + name?: Prisma.StringFilter<"CheckListTemplate"> | string + scene?: Prisma.StringFilter<"CheckListTemplate"> | string + items?: Prisma.JsonFilter<"CheckListTemplate"> + builtin?: Prisma.BoolFilter<"CheckListTemplate"> | boolean + createdAt?: Prisma.DateTimeFilter<"CheckListTemplate"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"CheckListTemplate"> | Date | string + user?: Prisma.XOR | null +} + +export type CheckListTemplateOrderByWithRelationInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrderInput | Prisma.SortOrder + name?: Prisma.SortOrder + scene?: Prisma.SortOrder + items?: Prisma.SortOrder + builtin?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + user?: Prisma.UserOrderByWithRelationInput +} + +export type CheckListTemplateWhereUniqueInput = Prisma.AtLeast<{ + id?: number + AND?: Prisma.CheckListTemplateWhereInput | Prisma.CheckListTemplateWhereInput[] + OR?: Prisma.CheckListTemplateWhereInput[] + NOT?: Prisma.CheckListTemplateWhereInput | Prisma.CheckListTemplateWhereInput[] + userId?: Prisma.IntNullableFilter<"CheckListTemplate"> | number | null + name?: Prisma.StringFilter<"CheckListTemplate"> | string + scene?: Prisma.StringFilter<"CheckListTemplate"> | string + items?: Prisma.JsonFilter<"CheckListTemplate"> + builtin?: Prisma.BoolFilter<"CheckListTemplate"> | boolean + createdAt?: Prisma.DateTimeFilter<"CheckListTemplate"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"CheckListTemplate"> | Date | string + user?: Prisma.XOR | null +}, "id"> + +export type CheckListTemplateOrderByWithAggregationInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrderInput | Prisma.SortOrder + name?: Prisma.SortOrder + scene?: Prisma.SortOrder + items?: Prisma.SortOrder + builtin?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + _count?: Prisma.CheckListTemplateCountOrderByAggregateInput + _avg?: Prisma.CheckListTemplateAvgOrderByAggregateInput + _max?: Prisma.CheckListTemplateMaxOrderByAggregateInput + _min?: Prisma.CheckListTemplateMinOrderByAggregateInput + _sum?: Prisma.CheckListTemplateSumOrderByAggregateInput +} + +export type CheckListTemplateScalarWhereWithAggregatesInput = { + AND?: Prisma.CheckListTemplateScalarWhereWithAggregatesInput | Prisma.CheckListTemplateScalarWhereWithAggregatesInput[] + OR?: Prisma.CheckListTemplateScalarWhereWithAggregatesInput[] + NOT?: Prisma.CheckListTemplateScalarWhereWithAggregatesInput | Prisma.CheckListTemplateScalarWhereWithAggregatesInput[] + id?: Prisma.IntWithAggregatesFilter<"CheckListTemplate"> | number + userId?: Prisma.IntNullableWithAggregatesFilter<"CheckListTemplate"> | number | null + name?: Prisma.StringWithAggregatesFilter<"CheckListTemplate"> | string + scene?: Prisma.StringWithAggregatesFilter<"CheckListTemplate"> | string + items?: Prisma.JsonWithAggregatesFilter<"CheckListTemplate"> + builtin?: Prisma.BoolWithAggregatesFilter<"CheckListTemplate"> | boolean + createdAt?: Prisma.DateTimeWithAggregatesFilter<"CheckListTemplate"> | Date | string + updatedAt?: Prisma.DateTimeWithAggregatesFilter<"CheckListTemplate"> | Date | string +} + +export type CheckListTemplateCreateInput = { + name: string + scene?: string + items: Prisma.JsonNullValueInput | runtime.InputJsonValue + builtin?: boolean + createdAt?: Date | string + updatedAt?: Date | string + user?: Prisma.UserCreateNestedOneWithoutTemplatesInput +} + +export type CheckListTemplateUncheckedCreateInput = { + id?: number + userId?: number | null + name: string + scene?: string + items: Prisma.JsonNullValueInput | runtime.InputJsonValue + builtin?: boolean + createdAt?: Date | string + updatedAt?: Date | string +} + +export type CheckListTemplateUpdateInput = { + name?: Prisma.StringFieldUpdateOperationsInput | string + scene?: Prisma.StringFieldUpdateOperationsInput | string + items?: Prisma.JsonNullValueInput | runtime.InputJsonValue + builtin?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + user?: Prisma.UserUpdateOneWithoutTemplatesNestedInput +} + +export type CheckListTemplateUncheckedUpdateInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + name?: Prisma.StringFieldUpdateOperationsInput | string + scene?: Prisma.StringFieldUpdateOperationsInput | string + items?: Prisma.JsonNullValueInput | runtime.InputJsonValue + builtin?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CheckListTemplateCreateManyInput = { + id?: number + userId?: number | null + name: string + scene?: string + items: Prisma.JsonNullValueInput | runtime.InputJsonValue + builtin?: boolean + createdAt?: Date | string + updatedAt?: Date | string +} + +export type CheckListTemplateUpdateManyMutationInput = { + name?: Prisma.StringFieldUpdateOperationsInput | string + scene?: Prisma.StringFieldUpdateOperationsInput | string + items?: Prisma.JsonNullValueInput | runtime.InputJsonValue + builtin?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CheckListTemplateUncheckedUpdateManyInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + name?: Prisma.StringFieldUpdateOperationsInput | string + scene?: Prisma.StringFieldUpdateOperationsInput | string + items?: Prisma.JsonNullValueInput | runtime.InputJsonValue + builtin?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CheckListTemplateListRelationFilter = { + every?: Prisma.CheckListTemplateWhereInput + some?: Prisma.CheckListTemplateWhereInput + none?: Prisma.CheckListTemplateWhereInput +} + +export type CheckListTemplateOrderByRelationAggregateInput = { + _count?: Prisma.SortOrder +} + +export type CheckListTemplateCountOrderByAggregateInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrder + name?: Prisma.SortOrder + scene?: Prisma.SortOrder + items?: Prisma.SortOrder + builtin?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type CheckListTemplateAvgOrderByAggregateInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrder +} + +export type CheckListTemplateMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrder + name?: Prisma.SortOrder + scene?: Prisma.SortOrder + builtin?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type CheckListTemplateMinOrderByAggregateInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrder + name?: Prisma.SortOrder + scene?: Prisma.SortOrder + builtin?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type CheckListTemplateSumOrderByAggregateInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrder +} + +export type CheckListTemplateCreateNestedManyWithoutUserInput = { + create?: Prisma.XOR | Prisma.CheckListTemplateCreateWithoutUserInput[] | Prisma.CheckListTemplateUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.CheckListTemplateCreateOrConnectWithoutUserInput | Prisma.CheckListTemplateCreateOrConnectWithoutUserInput[] + createMany?: Prisma.CheckListTemplateCreateManyUserInputEnvelope + connect?: Prisma.CheckListTemplateWhereUniqueInput | Prisma.CheckListTemplateWhereUniqueInput[] +} + +export type CheckListTemplateUncheckedCreateNestedManyWithoutUserInput = { + create?: Prisma.XOR | Prisma.CheckListTemplateCreateWithoutUserInput[] | Prisma.CheckListTemplateUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.CheckListTemplateCreateOrConnectWithoutUserInput | Prisma.CheckListTemplateCreateOrConnectWithoutUserInput[] + createMany?: Prisma.CheckListTemplateCreateManyUserInputEnvelope + connect?: Prisma.CheckListTemplateWhereUniqueInput | Prisma.CheckListTemplateWhereUniqueInput[] +} + +export type CheckListTemplateUpdateManyWithoutUserNestedInput = { + create?: Prisma.XOR | Prisma.CheckListTemplateCreateWithoutUserInput[] | Prisma.CheckListTemplateUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.CheckListTemplateCreateOrConnectWithoutUserInput | Prisma.CheckListTemplateCreateOrConnectWithoutUserInput[] + upsert?: Prisma.CheckListTemplateUpsertWithWhereUniqueWithoutUserInput | Prisma.CheckListTemplateUpsertWithWhereUniqueWithoutUserInput[] + createMany?: Prisma.CheckListTemplateCreateManyUserInputEnvelope + set?: Prisma.CheckListTemplateWhereUniqueInput | Prisma.CheckListTemplateWhereUniqueInput[] + disconnect?: Prisma.CheckListTemplateWhereUniqueInput | Prisma.CheckListTemplateWhereUniqueInput[] + delete?: Prisma.CheckListTemplateWhereUniqueInput | Prisma.CheckListTemplateWhereUniqueInput[] + connect?: Prisma.CheckListTemplateWhereUniqueInput | Prisma.CheckListTemplateWhereUniqueInput[] + update?: Prisma.CheckListTemplateUpdateWithWhereUniqueWithoutUserInput | Prisma.CheckListTemplateUpdateWithWhereUniqueWithoutUserInput[] + updateMany?: Prisma.CheckListTemplateUpdateManyWithWhereWithoutUserInput | Prisma.CheckListTemplateUpdateManyWithWhereWithoutUserInput[] + deleteMany?: Prisma.CheckListTemplateScalarWhereInput | Prisma.CheckListTemplateScalarWhereInput[] +} + +export type CheckListTemplateUncheckedUpdateManyWithoutUserNestedInput = { + create?: Prisma.XOR | Prisma.CheckListTemplateCreateWithoutUserInput[] | Prisma.CheckListTemplateUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.CheckListTemplateCreateOrConnectWithoutUserInput | Prisma.CheckListTemplateCreateOrConnectWithoutUserInput[] + upsert?: Prisma.CheckListTemplateUpsertWithWhereUniqueWithoutUserInput | Prisma.CheckListTemplateUpsertWithWhereUniqueWithoutUserInput[] + createMany?: Prisma.CheckListTemplateCreateManyUserInputEnvelope + set?: Prisma.CheckListTemplateWhereUniqueInput | Prisma.CheckListTemplateWhereUniqueInput[] + disconnect?: Prisma.CheckListTemplateWhereUniqueInput | Prisma.CheckListTemplateWhereUniqueInput[] + delete?: Prisma.CheckListTemplateWhereUniqueInput | Prisma.CheckListTemplateWhereUniqueInput[] + connect?: Prisma.CheckListTemplateWhereUniqueInput | Prisma.CheckListTemplateWhereUniqueInput[] + update?: Prisma.CheckListTemplateUpdateWithWhereUniqueWithoutUserInput | Prisma.CheckListTemplateUpdateWithWhereUniqueWithoutUserInput[] + updateMany?: Prisma.CheckListTemplateUpdateManyWithWhereWithoutUserInput | Prisma.CheckListTemplateUpdateManyWithWhereWithoutUserInput[] + deleteMany?: Prisma.CheckListTemplateScalarWhereInput | Prisma.CheckListTemplateScalarWhereInput[] +} + +export type CheckListTemplateCreateWithoutUserInput = { + name: string + scene?: string + items: Prisma.JsonNullValueInput | runtime.InputJsonValue + builtin?: boolean + createdAt?: Date | string + updatedAt?: Date | string +} + +export type CheckListTemplateUncheckedCreateWithoutUserInput = { + id?: number + name: string + scene?: string + items: Prisma.JsonNullValueInput | runtime.InputJsonValue + builtin?: boolean + createdAt?: Date | string + updatedAt?: Date | string +} + +export type CheckListTemplateCreateOrConnectWithoutUserInput = { + where: Prisma.CheckListTemplateWhereUniqueInput + create: Prisma.XOR +} + +export type CheckListTemplateCreateManyUserInputEnvelope = { + data: Prisma.CheckListTemplateCreateManyUserInput | Prisma.CheckListTemplateCreateManyUserInput[] + skipDuplicates?: boolean +} + +export type CheckListTemplateUpsertWithWhereUniqueWithoutUserInput = { + where: Prisma.CheckListTemplateWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type CheckListTemplateUpdateWithWhereUniqueWithoutUserInput = { + where: Prisma.CheckListTemplateWhereUniqueInput + data: Prisma.XOR +} + +export type CheckListTemplateUpdateManyWithWhereWithoutUserInput = { + where: Prisma.CheckListTemplateScalarWhereInput + data: Prisma.XOR +} + +export type CheckListTemplateScalarWhereInput = { + AND?: Prisma.CheckListTemplateScalarWhereInput | Prisma.CheckListTemplateScalarWhereInput[] + OR?: Prisma.CheckListTemplateScalarWhereInput[] + NOT?: Prisma.CheckListTemplateScalarWhereInput | Prisma.CheckListTemplateScalarWhereInput[] + id?: Prisma.IntFilter<"CheckListTemplate"> | number + userId?: Prisma.IntNullableFilter<"CheckListTemplate"> | number | null + name?: Prisma.StringFilter<"CheckListTemplate"> | string + scene?: Prisma.StringFilter<"CheckListTemplate"> | string + items?: Prisma.JsonFilter<"CheckListTemplate"> + builtin?: Prisma.BoolFilter<"CheckListTemplate"> | boolean + createdAt?: Prisma.DateTimeFilter<"CheckListTemplate"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"CheckListTemplate"> | Date | string +} + +export type CheckListTemplateCreateManyUserInput = { + id?: number + name: string + scene?: string + items: Prisma.JsonNullValueInput | runtime.InputJsonValue + builtin?: boolean + createdAt?: Date | string + updatedAt?: Date | string +} + +export type CheckListTemplateUpdateWithoutUserInput = { + name?: Prisma.StringFieldUpdateOperationsInput | string + scene?: Prisma.StringFieldUpdateOperationsInput | string + items?: Prisma.JsonNullValueInput | runtime.InputJsonValue + builtin?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CheckListTemplateUncheckedUpdateWithoutUserInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string + scene?: Prisma.StringFieldUpdateOperationsInput | string + items?: Prisma.JsonNullValueInput | runtime.InputJsonValue + builtin?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CheckListTemplateUncheckedUpdateManyWithoutUserInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string + scene?: Prisma.StringFieldUpdateOperationsInput | string + items?: Prisma.JsonNullValueInput | runtime.InputJsonValue + builtin?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + + + +export type CheckListTemplateSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + userId?: boolean + name?: boolean + scene?: boolean + items?: boolean + builtin?: boolean + createdAt?: boolean + updatedAt?: boolean + user?: boolean | Prisma.CheckListTemplate$userArgs +}, ExtArgs["result"]["checkListTemplate"]> + +export type CheckListTemplateSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + userId?: boolean + name?: boolean + scene?: boolean + items?: boolean + builtin?: boolean + createdAt?: boolean + updatedAt?: boolean + user?: boolean | Prisma.CheckListTemplate$userArgs +}, ExtArgs["result"]["checkListTemplate"]> + +export type CheckListTemplateSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + userId?: boolean + name?: boolean + scene?: boolean + items?: boolean + builtin?: boolean + createdAt?: boolean + updatedAt?: boolean + user?: boolean | Prisma.CheckListTemplate$userArgs +}, ExtArgs["result"]["checkListTemplate"]> + +export type CheckListTemplateSelectScalar = { + id?: boolean + userId?: boolean + name?: boolean + scene?: boolean + items?: boolean + builtin?: boolean + createdAt?: boolean + updatedAt?: boolean +} + +export type CheckListTemplateOmit = runtime.Types.Extensions.GetOmit<"id" | "userId" | "name" | "scene" | "items" | "builtin" | "createdAt" | "updatedAt", ExtArgs["result"]["checkListTemplate"]> +export type CheckListTemplateInclude = { + user?: boolean | Prisma.CheckListTemplate$userArgs +} +export type CheckListTemplateIncludeCreateManyAndReturn = { + user?: boolean | Prisma.CheckListTemplate$userArgs +} +export type CheckListTemplateIncludeUpdateManyAndReturn = { + user?: boolean | Prisma.CheckListTemplate$userArgs +} + +export type $CheckListTemplatePayload = { + name: "CheckListTemplate" + objects: { + user: Prisma.$UserPayload | null + } + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: number + userId: number | null + name: string + scene: string + items: runtime.JsonValue + builtin: boolean + createdAt: Date + updatedAt: Date + }, ExtArgs["result"]["checkListTemplate"]> + composites: {} +} + +export type CheckListTemplateGetPayload = runtime.Types.Result.GetResult + +export type CheckListTemplateCountArgs = + Omit & { + select?: CheckListTemplateCountAggregateInputType | true + } + +export interface CheckListTemplateDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['CheckListTemplate'], meta: { name: 'CheckListTemplate' } } + /** + * Find zero or one CheckListTemplate that matches the filter. + * @param {CheckListTemplateFindUniqueArgs} args - Arguments to find a CheckListTemplate + * @example + * // Get one CheckListTemplate + * const checkListTemplate = await prisma.checkListTemplate.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__CheckListTemplateClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one CheckListTemplate that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {CheckListTemplateFindUniqueOrThrowArgs} args - Arguments to find a CheckListTemplate + * @example + * // Get one CheckListTemplate + * const checkListTemplate = await prisma.checkListTemplate.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__CheckListTemplateClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first CheckListTemplate that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListTemplateFindFirstArgs} args - Arguments to find a CheckListTemplate + * @example + * // Get one CheckListTemplate + * const checkListTemplate = await prisma.checkListTemplate.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__CheckListTemplateClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first CheckListTemplate that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListTemplateFindFirstOrThrowArgs} args - Arguments to find a CheckListTemplate + * @example + * // Get one CheckListTemplate + * const checkListTemplate = await prisma.checkListTemplate.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__CheckListTemplateClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more CheckListTemplates that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListTemplateFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all CheckListTemplates + * const checkListTemplates = await prisma.checkListTemplate.findMany() + * + * // Get first 10 CheckListTemplates + * const checkListTemplates = await prisma.checkListTemplate.findMany({ take: 10 }) + * + * // Only select the `id` + * const checkListTemplateWithIdOnly = await prisma.checkListTemplate.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a CheckListTemplate. + * @param {CheckListTemplateCreateArgs} args - Arguments to create a CheckListTemplate. + * @example + * // Create one CheckListTemplate + * const CheckListTemplate = await prisma.checkListTemplate.create({ + * data: { + * // ... data to create a CheckListTemplate + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__CheckListTemplateClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many CheckListTemplates. + * @param {CheckListTemplateCreateManyArgs} args - Arguments to create many CheckListTemplates. + * @example + * // Create many CheckListTemplates + * const checkListTemplate = await prisma.checkListTemplate.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many CheckListTemplates and returns the data saved in the database. + * @param {CheckListTemplateCreateManyAndReturnArgs} args - Arguments to create many CheckListTemplates. + * @example + * // Create many CheckListTemplates + * const checkListTemplate = await prisma.checkListTemplate.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many CheckListTemplates and only return the `id` + * const checkListTemplateWithIdOnly = await prisma.checkListTemplate.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a CheckListTemplate. + * @param {CheckListTemplateDeleteArgs} args - Arguments to delete one CheckListTemplate. + * @example + * // Delete one CheckListTemplate + * const CheckListTemplate = await prisma.checkListTemplate.delete({ + * where: { + * // ... filter to delete one CheckListTemplate + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__CheckListTemplateClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one CheckListTemplate. + * @param {CheckListTemplateUpdateArgs} args - Arguments to update one CheckListTemplate. + * @example + * // Update one CheckListTemplate + * const checkListTemplate = await prisma.checkListTemplate.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__CheckListTemplateClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more CheckListTemplates. + * @param {CheckListTemplateDeleteManyArgs} args - Arguments to filter CheckListTemplates to delete. + * @example + * // Delete a few CheckListTemplates + * const { count } = await prisma.checkListTemplate.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more CheckListTemplates. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListTemplateUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many CheckListTemplates + * const checkListTemplate = await prisma.checkListTemplate.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more CheckListTemplates and returns the data updated in the database. + * @param {CheckListTemplateUpdateManyAndReturnArgs} args - Arguments to update many CheckListTemplates. + * @example + * // Update many CheckListTemplates + * const checkListTemplate = await prisma.checkListTemplate.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more CheckListTemplates and only return the `id` + * const checkListTemplateWithIdOnly = await prisma.checkListTemplate.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one CheckListTemplate. + * @param {CheckListTemplateUpsertArgs} args - Arguments to update or create a CheckListTemplate. + * @example + * // Update or create a CheckListTemplate + * const checkListTemplate = await prisma.checkListTemplate.upsert({ + * create: { + * // ... data to create a CheckListTemplate + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the CheckListTemplate we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__CheckListTemplateClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of CheckListTemplates. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListTemplateCountArgs} args - Arguments to filter CheckListTemplates to count. + * @example + * // Count the number of CheckListTemplates + * const count = await prisma.checkListTemplate.count({ + * where: { + * // ... the filter for the CheckListTemplates we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a CheckListTemplate. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListTemplateAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by CheckListTemplate. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CheckListTemplateGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends CheckListTemplateGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: CheckListTemplateGroupByArgs['orderBy'] } + : { orderBy?: CheckListTemplateGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetCheckListTemplateGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the CheckListTemplate model + */ +readonly fields: CheckListTemplateFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for CheckListTemplate. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__CheckListTemplateClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + user = {}>(args?: Prisma.Subset>): Prisma.Prisma__UserClient, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the CheckListTemplate model + */ +export interface CheckListTemplateFieldRefs { + readonly id: Prisma.FieldRef<"CheckListTemplate", 'Int'> + readonly userId: Prisma.FieldRef<"CheckListTemplate", 'Int'> + readonly name: Prisma.FieldRef<"CheckListTemplate", 'String'> + readonly scene: Prisma.FieldRef<"CheckListTemplate", 'String'> + readonly items: Prisma.FieldRef<"CheckListTemplate", 'Json'> + readonly builtin: Prisma.FieldRef<"CheckListTemplate", 'Boolean'> + readonly createdAt: Prisma.FieldRef<"CheckListTemplate", 'DateTime'> + readonly updatedAt: Prisma.FieldRef<"CheckListTemplate", 'DateTime'> +} + + +// Custom InputTypes +/** + * CheckListTemplate findUnique + */ +export type CheckListTemplateFindUniqueArgs = { + /** + * Select specific fields to fetch from the CheckListTemplate + */ + select?: Prisma.CheckListTemplateSelect | null + /** + * Omit specific fields from the CheckListTemplate + */ + omit?: Prisma.CheckListTemplateOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListTemplateInclude | null + /** + * Filter, which CheckListTemplate to fetch. + */ + where: Prisma.CheckListTemplateWhereUniqueInput +} + +/** + * CheckListTemplate findUniqueOrThrow + */ +export type CheckListTemplateFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the CheckListTemplate + */ + select?: Prisma.CheckListTemplateSelect | null + /** + * Omit specific fields from the CheckListTemplate + */ + omit?: Prisma.CheckListTemplateOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListTemplateInclude | null + /** + * Filter, which CheckListTemplate to fetch. + */ + where: Prisma.CheckListTemplateWhereUniqueInput +} + +/** + * CheckListTemplate findFirst + */ +export type CheckListTemplateFindFirstArgs = { + /** + * Select specific fields to fetch from the CheckListTemplate + */ + select?: Prisma.CheckListTemplateSelect | null + /** + * Omit specific fields from the CheckListTemplate + */ + omit?: Prisma.CheckListTemplateOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListTemplateInclude | null + /** + * Filter, which CheckListTemplate to fetch. + */ + where?: Prisma.CheckListTemplateWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CheckListTemplates to fetch. + */ + orderBy?: Prisma.CheckListTemplateOrderByWithRelationInput | Prisma.CheckListTemplateOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for CheckListTemplates. + */ + cursor?: Prisma.CheckListTemplateWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CheckListTemplates from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CheckListTemplates. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of CheckListTemplates. + */ + distinct?: Prisma.CheckListTemplateScalarFieldEnum | Prisma.CheckListTemplateScalarFieldEnum[] +} + +/** + * CheckListTemplate findFirstOrThrow + */ +export type CheckListTemplateFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the CheckListTemplate + */ + select?: Prisma.CheckListTemplateSelect | null + /** + * Omit specific fields from the CheckListTemplate + */ + omit?: Prisma.CheckListTemplateOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListTemplateInclude | null + /** + * Filter, which CheckListTemplate to fetch. + */ + where?: Prisma.CheckListTemplateWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CheckListTemplates to fetch. + */ + orderBy?: Prisma.CheckListTemplateOrderByWithRelationInput | Prisma.CheckListTemplateOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for CheckListTemplates. + */ + cursor?: Prisma.CheckListTemplateWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CheckListTemplates from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CheckListTemplates. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of CheckListTemplates. + */ + distinct?: Prisma.CheckListTemplateScalarFieldEnum | Prisma.CheckListTemplateScalarFieldEnum[] +} + +/** + * CheckListTemplate findMany + */ +export type CheckListTemplateFindManyArgs = { + /** + * Select specific fields to fetch from the CheckListTemplate + */ + select?: Prisma.CheckListTemplateSelect | null + /** + * Omit specific fields from the CheckListTemplate + */ + omit?: Prisma.CheckListTemplateOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListTemplateInclude | null + /** + * Filter, which CheckListTemplates to fetch. + */ + where?: Prisma.CheckListTemplateWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CheckListTemplates to fetch. + */ + orderBy?: Prisma.CheckListTemplateOrderByWithRelationInput | Prisma.CheckListTemplateOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing CheckListTemplates. + */ + cursor?: Prisma.CheckListTemplateWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CheckListTemplates from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CheckListTemplates. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of CheckListTemplates. + */ + distinct?: Prisma.CheckListTemplateScalarFieldEnum | Prisma.CheckListTemplateScalarFieldEnum[] +} + +/** + * CheckListTemplate create + */ +export type CheckListTemplateCreateArgs = { + /** + * Select specific fields to fetch from the CheckListTemplate + */ + select?: Prisma.CheckListTemplateSelect | null + /** + * Omit specific fields from the CheckListTemplate + */ + omit?: Prisma.CheckListTemplateOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListTemplateInclude | null + /** + * The data needed to create a CheckListTemplate. + */ + data: Prisma.XOR +} + +/** + * CheckListTemplate createMany + */ +export type CheckListTemplateCreateManyArgs = { + /** + * The data used to create many CheckListTemplates. + */ + data: Prisma.CheckListTemplateCreateManyInput | Prisma.CheckListTemplateCreateManyInput[] + skipDuplicates?: boolean +} + +/** + * CheckListTemplate createManyAndReturn + */ +export type CheckListTemplateCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the CheckListTemplate + */ + select?: Prisma.CheckListTemplateSelectCreateManyAndReturn | null + /** + * Omit specific fields from the CheckListTemplate + */ + omit?: Prisma.CheckListTemplateOmit | null + /** + * The data used to create many CheckListTemplates. + */ + data: Prisma.CheckListTemplateCreateManyInput | Prisma.CheckListTemplateCreateManyInput[] + skipDuplicates?: boolean + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListTemplateIncludeCreateManyAndReturn | null +} + +/** + * CheckListTemplate update + */ +export type CheckListTemplateUpdateArgs = { + /** + * Select specific fields to fetch from the CheckListTemplate + */ + select?: Prisma.CheckListTemplateSelect | null + /** + * Omit specific fields from the CheckListTemplate + */ + omit?: Prisma.CheckListTemplateOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListTemplateInclude | null + /** + * The data needed to update a CheckListTemplate. + */ + data: Prisma.XOR + /** + * Choose, which CheckListTemplate to update. + */ + where: Prisma.CheckListTemplateWhereUniqueInput +} + +/** + * CheckListTemplate updateMany + */ +export type CheckListTemplateUpdateManyArgs = { + /** + * The data used to update CheckListTemplates. + */ + data: Prisma.XOR + /** + * Filter which CheckListTemplates to update + */ + where?: Prisma.CheckListTemplateWhereInput + /** + * Limit how many CheckListTemplates to update. + */ + limit?: number +} + +/** + * CheckListTemplate updateManyAndReturn + */ +export type CheckListTemplateUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the CheckListTemplate + */ + select?: Prisma.CheckListTemplateSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the CheckListTemplate + */ + omit?: Prisma.CheckListTemplateOmit | null + /** + * The data used to update CheckListTemplates. + */ + data: Prisma.XOR + /** + * Filter which CheckListTemplates to update + */ + where?: Prisma.CheckListTemplateWhereInput + /** + * Limit how many CheckListTemplates to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListTemplateIncludeUpdateManyAndReturn | null +} + +/** + * CheckListTemplate upsert + */ +export type CheckListTemplateUpsertArgs = { + /** + * Select specific fields to fetch from the CheckListTemplate + */ + select?: Prisma.CheckListTemplateSelect | null + /** + * Omit specific fields from the CheckListTemplate + */ + omit?: Prisma.CheckListTemplateOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListTemplateInclude | null + /** + * The filter to search for the CheckListTemplate to update in case it exists. + */ + where: Prisma.CheckListTemplateWhereUniqueInput + /** + * In case the CheckListTemplate found by the `where` argument doesn't exist, create a new CheckListTemplate with this data. + */ + create: Prisma.XOR + /** + * In case the CheckListTemplate was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * CheckListTemplate delete + */ +export type CheckListTemplateDeleteArgs = { + /** + * Select specific fields to fetch from the CheckListTemplate + */ + select?: Prisma.CheckListTemplateSelect | null + /** + * Omit specific fields from the CheckListTemplate + */ + omit?: Prisma.CheckListTemplateOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListTemplateInclude | null + /** + * Filter which CheckListTemplate to delete. + */ + where: Prisma.CheckListTemplateWhereUniqueInput +} + +/** + * CheckListTemplate deleteMany + */ +export type CheckListTemplateDeleteManyArgs = { + /** + * Filter which CheckListTemplates to delete + */ + where?: Prisma.CheckListTemplateWhereInput + /** + * Limit how many CheckListTemplates to delete. + */ + limit?: number +} + +/** + * CheckListTemplate.user + */ +export type CheckListTemplate$userArgs = { + /** + * Select specific fields to fetch from the User + */ + select?: Prisma.UserSelect | null + /** + * Omit specific fields from the User + */ + omit?: Prisma.UserOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.UserInclude | null + where?: Prisma.UserWhereInput +} + +/** + * CheckListTemplate without action + */ +export type CheckListTemplateDefaultArgs = { + /** + * Select specific fields to fetch from the CheckListTemplate + */ + select?: Prisma.CheckListTemplateSelect | null + /** + * Omit specific fields from the CheckListTemplate + */ + omit?: Prisma.CheckListTemplateOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListTemplateInclude | null +} diff --git a/generated/prisma/models/Econ_SowInventory.ts b/generated/prisma/models/Econ_SowInventory.ts index d1bb041..913e025 100644 --- a/generated/prisma/models/Econ_SowInventory.ts +++ b/generated/prisma/models/Econ_SowInventory.ts @@ -228,7 +228,6 @@ export type Econ_SowInventoryOrderByWithRelationInput = { inventory?: Prisma.SortOrder createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder - _relevance?: Prisma.Econ_SowInventoryOrderByRelevanceInput } export type Econ_SowInventoryWhereUniqueInput = Prisma.AtLeast<{ @@ -319,12 +318,6 @@ export type Econ_SowInventoryUncheckedUpdateManyInput = { updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string } -export type Econ_SowInventoryOrderByRelevanceInput = { - fields: Prisma.Econ_SowInventoryOrderByRelevanceFieldEnum | Prisma.Econ_SowInventoryOrderByRelevanceFieldEnum[] - sort: Prisma.SortOrder - search: string -} - export type Econ_SowInventoryCountOrderByAggregateInput = { id?: Prisma.SortOrder month?: Prisma.SortOrder @@ -369,7 +362,21 @@ export type Econ_SowInventorySelect +export type Econ_SowInventorySelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + month?: boolean + inventory?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["econ_SowInventory"]> +export type Econ_SowInventorySelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + month?: boolean + inventory?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["econ_SowInventory"]> export type Econ_SowInventorySelectScalar = { id?: boolean @@ -507,6 +514,30 @@ export interface Econ_SowInventoryDelegate(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Create many Econ_SowInventories and returns the data saved in the database. + * @param {Econ_SowInventoryCreateManyAndReturnArgs} args - Arguments to create many Econ_SowInventories. + * @example + * // Create many Econ_SowInventories + * const econ_SowInventory = await prisma.econ_SowInventory.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many Econ_SowInventories and only return the `id` + * const econ_SowInventoryWithIdOnly = await prisma.econ_SowInventory.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + /** * Delete a Econ_SowInventory. * @param {Econ_SowInventoryDeleteArgs} args - Arguments to delete one Econ_SowInventory. @@ -571,6 +602,36 @@ export interface Econ_SowInventoryDelegate(args: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Update zero or more Econ_SowInventories and returns the data updated in the database. + * @param {Econ_SowInventoryUpdateManyAndReturnArgs} args - Arguments to update many Econ_SowInventories. + * @example + * // Update many Econ_SowInventories + * const econ_SowInventory = await prisma.econ_SowInventory.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more Econ_SowInventories and only return the `id` + * const econ_SowInventoryWithIdOnly = await prisma.econ_SowInventory.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + /** * Create or update one Econ_SowInventory. * @param {Econ_SowInventoryUpsertArgs} args - Arguments to update or create a Econ_SowInventory. @@ -977,6 +1038,25 @@ export type Econ_SowInventoryCreateManyArgs = { + /** + * Select specific fields to fetch from the Econ_SowInventory + */ + select?: Prisma.Econ_SowInventorySelectCreateManyAndReturn | null + /** + * Omit specific fields from the Econ_SowInventory + */ + omit?: Prisma.Econ_SowInventoryOmit | null + /** + * The data used to create many Econ_SowInventories. + */ + data: Prisma.Econ_SowInventoryCreateManyInput | Prisma.Econ_SowInventoryCreateManyInput[] + skipDuplicates?: boolean +} + /** * Econ_SowInventory update */ @@ -1017,6 +1097,32 @@ export type Econ_SowInventoryUpdateManyArgs = { + /** + * Select specific fields to fetch from the Econ_SowInventory + */ + select?: Prisma.Econ_SowInventorySelectUpdateManyAndReturn | null + /** + * Omit specific fields from the Econ_SowInventory + */ + omit?: Prisma.Econ_SowInventoryOmit | null + /** + * The data used to update Econ_SowInventories. + */ + data: Prisma.XOR + /** + * Filter which Econ_SowInventories to update + */ + where?: Prisma.Econ_SowInventoryWhereInput + /** + * Limit how many Econ_SowInventories to update. + */ + limit?: number +} + /** * Econ_SowInventory upsert */ diff --git a/generated/prisma/models/Habit.ts b/generated/prisma/models/Habit.ts new file mode 100644 index 0000000..7142af3 --- /dev/null +++ b/generated/prisma/models/Habit.ts @@ -0,0 +1,1810 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `Habit` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums.ts" +import type * as Prisma from "../internal/prismaNamespace.ts" + +/** + * Model Habit + * + */ +export type HabitModel = runtime.Types.Result.DefaultSelection + +export type AggregateHabit = { + _count: HabitCountAggregateOutputType | null + _avg: HabitAvgAggregateOutputType | null + _sum: HabitSumAggregateOutputType | null + _min: HabitMinAggregateOutputType | null + _max: HabitMaxAggregateOutputType | null +} + +export type HabitAvgAggregateOutputType = { + id: number | null + userId: number | null + target: number | null + sortOrder: number | null +} + +export type HabitSumAggregateOutputType = { + id: number | null + userId: number | null + target: number | null + sortOrder: number | null +} + +export type HabitMinAggregateOutputType = { + id: number | null + userId: number | null + name: string | null + period: string | null + target: number | null + color: string | null + icon: string | null + note: string | null + archived: boolean | null + sortOrder: number | null + createdAt: Date | null + updatedAt: Date | null +} + +export type HabitMaxAggregateOutputType = { + id: number | null + userId: number | null + name: string | null + period: string | null + target: number | null + color: string | null + icon: string | null + note: string | null + archived: boolean | null + sortOrder: number | null + createdAt: Date | null + updatedAt: Date | null +} + +export type HabitCountAggregateOutputType = { + id: number + userId: number + name: number + period: number + target: number + color: number + icon: number + note: number + archived: number + sortOrder: number + createdAt: number + updatedAt: number + _all: number +} + + +export type HabitAvgAggregateInputType = { + id?: true + userId?: true + target?: true + sortOrder?: true +} + +export type HabitSumAggregateInputType = { + id?: true + userId?: true + target?: true + sortOrder?: true +} + +export type HabitMinAggregateInputType = { + id?: true + userId?: true + name?: true + period?: true + target?: true + color?: true + icon?: true + note?: true + archived?: true + sortOrder?: true + createdAt?: true + updatedAt?: true +} + +export type HabitMaxAggregateInputType = { + id?: true + userId?: true + name?: true + period?: true + target?: true + color?: true + icon?: true + note?: true + archived?: true + sortOrder?: true + createdAt?: true + updatedAt?: true +} + +export type HabitCountAggregateInputType = { + id?: true + userId?: true + name?: true + period?: true + target?: true + color?: true + icon?: true + note?: true + archived?: true + sortOrder?: true + createdAt?: true + updatedAt?: true + _all?: true +} + +export type HabitAggregateArgs = { + /** + * Filter which Habit to aggregate. + */ + where?: Prisma.HabitWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Habits to fetch. + */ + orderBy?: Prisma.HabitOrderByWithRelationInput | Prisma.HabitOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.HabitWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Habits from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Habits. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned Habits + **/ + _count?: true | HabitCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to average + **/ + _avg?: HabitAvgAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to sum + **/ + _sum?: HabitSumAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: HabitMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: HabitMaxAggregateInputType +} + +export type GetHabitAggregateType = { + [P in keyof T & keyof AggregateHabit]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type HabitGroupByArgs = { + where?: Prisma.HabitWhereInput + orderBy?: Prisma.HabitOrderByWithAggregationInput | Prisma.HabitOrderByWithAggregationInput[] + by: Prisma.HabitScalarFieldEnum[] | Prisma.HabitScalarFieldEnum + having?: Prisma.HabitScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: HabitCountAggregateInputType | true + _avg?: HabitAvgAggregateInputType + _sum?: HabitSumAggregateInputType + _min?: HabitMinAggregateInputType + _max?: HabitMaxAggregateInputType +} + +export type HabitGroupByOutputType = { + id: number + userId: number + name: string + period: string + target: number + color: string + icon: string + note: string + archived: boolean + sortOrder: number + createdAt: Date + updatedAt: Date + _count: HabitCountAggregateOutputType | null + _avg: HabitAvgAggregateOutputType | null + _sum: HabitSumAggregateOutputType | null + _min: HabitMinAggregateOutputType | null + _max: HabitMaxAggregateOutputType | null +} + +export type GetHabitGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof HabitGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type HabitWhereInput = { + AND?: Prisma.HabitWhereInput | Prisma.HabitWhereInput[] + OR?: Prisma.HabitWhereInput[] + NOT?: Prisma.HabitWhereInput | Prisma.HabitWhereInput[] + id?: Prisma.IntFilter<"Habit"> | number + userId?: Prisma.IntFilter<"Habit"> | number + name?: Prisma.StringFilter<"Habit"> | string + period?: Prisma.StringFilter<"Habit"> | string + target?: Prisma.IntFilter<"Habit"> | number + color?: Prisma.StringFilter<"Habit"> | string + icon?: Prisma.StringFilter<"Habit"> | string + note?: Prisma.StringFilter<"Habit"> | string + archived?: Prisma.BoolFilter<"Habit"> | boolean + sortOrder?: Prisma.IntFilter<"Habit"> | number + createdAt?: Prisma.DateTimeFilter<"Habit"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"Habit"> | Date | string + user?: Prisma.XOR + records?: Prisma.CheckInListRelationFilter +} + +export type HabitOrderByWithRelationInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrder + name?: Prisma.SortOrder + period?: Prisma.SortOrder + target?: Prisma.SortOrder + color?: Prisma.SortOrder + icon?: Prisma.SortOrder + note?: Prisma.SortOrder + archived?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + user?: Prisma.UserOrderByWithRelationInput + records?: Prisma.CheckInOrderByRelationAggregateInput +} + +export type HabitWhereUniqueInput = Prisma.AtLeast<{ + id?: number + AND?: Prisma.HabitWhereInput | Prisma.HabitWhereInput[] + OR?: Prisma.HabitWhereInput[] + NOT?: Prisma.HabitWhereInput | Prisma.HabitWhereInput[] + userId?: Prisma.IntFilter<"Habit"> | number + name?: Prisma.StringFilter<"Habit"> | string + period?: Prisma.StringFilter<"Habit"> | string + target?: Prisma.IntFilter<"Habit"> | number + color?: Prisma.StringFilter<"Habit"> | string + icon?: Prisma.StringFilter<"Habit"> | string + note?: Prisma.StringFilter<"Habit"> | string + archived?: Prisma.BoolFilter<"Habit"> | boolean + sortOrder?: Prisma.IntFilter<"Habit"> | number + createdAt?: Prisma.DateTimeFilter<"Habit"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"Habit"> | Date | string + user?: Prisma.XOR + records?: Prisma.CheckInListRelationFilter +}, "id"> + +export type HabitOrderByWithAggregationInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrder + name?: Prisma.SortOrder + period?: Prisma.SortOrder + target?: Prisma.SortOrder + color?: Prisma.SortOrder + icon?: Prisma.SortOrder + note?: Prisma.SortOrder + archived?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + _count?: Prisma.HabitCountOrderByAggregateInput + _avg?: Prisma.HabitAvgOrderByAggregateInput + _max?: Prisma.HabitMaxOrderByAggregateInput + _min?: Prisma.HabitMinOrderByAggregateInput + _sum?: Prisma.HabitSumOrderByAggregateInput +} + +export type HabitScalarWhereWithAggregatesInput = { + AND?: Prisma.HabitScalarWhereWithAggregatesInput | Prisma.HabitScalarWhereWithAggregatesInput[] + OR?: Prisma.HabitScalarWhereWithAggregatesInput[] + NOT?: Prisma.HabitScalarWhereWithAggregatesInput | Prisma.HabitScalarWhereWithAggregatesInput[] + id?: Prisma.IntWithAggregatesFilter<"Habit"> | number + userId?: Prisma.IntWithAggregatesFilter<"Habit"> | number + name?: Prisma.StringWithAggregatesFilter<"Habit"> | string + period?: Prisma.StringWithAggregatesFilter<"Habit"> | string + target?: Prisma.IntWithAggregatesFilter<"Habit"> | number + color?: Prisma.StringWithAggregatesFilter<"Habit"> | string + icon?: Prisma.StringWithAggregatesFilter<"Habit"> | string + note?: Prisma.StringWithAggregatesFilter<"Habit"> | string + archived?: Prisma.BoolWithAggregatesFilter<"Habit"> | boolean + sortOrder?: Prisma.IntWithAggregatesFilter<"Habit"> | number + createdAt?: Prisma.DateTimeWithAggregatesFilter<"Habit"> | Date | string + updatedAt?: Prisma.DateTimeWithAggregatesFilter<"Habit"> | Date | string +} + +export type HabitCreateInput = { + name: string + period?: string + target?: number + color?: string + icon?: string + note?: string + archived?: boolean + sortOrder?: number + createdAt?: Date | string + updatedAt?: Date | string + user: Prisma.UserCreateNestedOneWithoutHabitsInput + records?: Prisma.CheckInCreateNestedManyWithoutHabitInput +} + +export type HabitUncheckedCreateInput = { + id?: number + userId: number + name: string + period?: string + target?: number + color?: string + icon?: string + note?: string + archived?: boolean + sortOrder?: number + createdAt?: Date | string + updatedAt?: Date | string + records?: Prisma.CheckInUncheckedCreateNestedManyWithoutHabitInput +} + +export type HabitUpdateInput = { + name?: Prisma.StringFieldUpdateOperationsInput | string + period?: Prisma.StringFieldUpdateOperationsInput | string + target?: Prisma.IntFieldUpdateOperationsInput | number + color?: Prisma.StringFieldUpdateOperationsInput | string + icon?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + user?: Prisma.UserUpdateOneRequiredWithoutHabitsNestedInput + records?: Prisma.CheckInUpdateManyWithoutHabitNestedInput +} + +export type HabitUncheckedUpdateInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string + period?: Prisma.StringFieldUpdateOperationsInput | string + target?: Prisma.IntFieldUpdateOperationsInput | number + color?: Prisma.StringFieldUpdateOperationsInput | string + icon?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + records?: Prisma.CheckInUncheckedUpdateManyWithoutHabitNestedInput +} + +export type HabitCreateManyInput = { + id?: number + userId: number + name: string + period?: string + target?: number + color?: string + icon?: string + note?: string + archived?: boolean + sortOrder?: number + createdAt?: Date | string + updatedAt?: Date | string +} + +export type HabitUpdateManyMutationInput = { + name?: Prisma.StringFieldUpdateOperationsInput | string + period?: Prisma.StringFieldUpdateOperationsInput | string + target?: Prisma.IntFieldUpdateOperationsInput | number + color?: Prisma.StringFieldUpdateOperationsInput | string + icon?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type HabitUncheckedUpdateManyInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string + period?: Prisma.StringFieldUpdateOperationsInput | string + target?: Prisma.IntFieldUpdateOperationsInput | number + color?: Prisma.StringFieldUpdateOperationsInput | string + icon?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type HabitListRelationFilter = { + every?: Prisma.HabitWhereInput + some?: Prisma.HabitWhereInput + none?: Prisma.HabitWhereInput +} + +export type HabitOrderByRelationAggregateInput = { + _count?: Prisma.SortOrder +} + +export type HabitCountOrderByAggregateInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrder + name?: Prisma.SortOrder + period?: Prisma.SortOrder + target?: Prisma.SortOrder + color?: Prisma.SortOrder + icon?: Prisma.SortOrder + note?: Prisma.SortOrder + archived?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type HabitAvgOrderByAggregateInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrder + target?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder +} + +export type HabitMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrder + name?: Prisma.SortOrder + period?: Prisma.SortOrder + target?: Prisma.SortOrder + color?: Prisma.SortOrder + icon?: Prisma.SortOrder + note?: Prisma.SortOrder + archived?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type HabitMinOrderByAggregateInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrder + name?: Prisma.SortOrder + period?: Prisma.SortOrder + target?: Prisma.SortOrder + color?: Prisma.SortOrder + icon?: Prisma.SortOrder + note?: Prisma.SortOrder + archived?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type HabitSumOrderByAggregateInput = { + id?: Prisma.SortOrder + userId?: Prisma.SortOrder + target?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder +} + +export type HabitScalarRelationFilter = { + is?: Prisma.HabitWhereInput + isNot?: Prisma.HabitWhereInput +} + +export type HabitCreateNestedManyWithoutUserInput = { + create?: Prisma.XOR | Prisma.HabitCreateWithoutUserInput[] | Prisma.HabitUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.HabitCreateOrConnectWithoutUserInput | Prisma.HabitCreateOrConnectWithoutUserInput[] + createMany?: Prisma.HabitCreateManyUserInputEnvelope + connect?: Prisma.HabitWhereUniqueInput | Prisma.HabitWhereUniqueInput[] +} + +export type HabitUncheckedCreateNestedManyWithoutUserInput = { + create?: Prisma.XOR | Prisma.HabitCreateWithoutUserInput[] | Prisma.HabitUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.HabitCreateOrConnectWithoutUserInput | Prisma.HabitCreateOrConnectWithoutUserInput[] + createMany?: Prisma.HabitCreateManyUserInputEnvelope + connect?: Prisma.HabitWhereUniqueInput | Prisma.HabitWhereUniqueInput[] +} + +export type HabitUpdateManyWithoutUserNestedInput = { + create?: Prisma.XOR | Prisma.HabitCreateWithoutUserInput[] | Prisma.HabitUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.HabitCreateOrConnectWithoutUserInput | Prisma.HabitCreateOrConnectWithoutUserInput[] + upsert?: Prisma.HabitUpsertWithWhereUniqueWithoutUserInput | Prisma.HabitUpsertWithWhereUniqueWithoutUserInput[] + createMany?: Prisma.HabitCreateManyUserInputEnvelope + set?: Prisma.HabitWhereUniqueInput | Prisma.HabitWhereUniqueInput[] + disconnect?: Prisma.HabitWhereUniqueInput | Prisma.HabitWhereUniqueInput[] + delete?: Prisma.HabitWhereUniqueInput | Prisma.HabitWhereUniqueInput[] + connect?: Prisma.HabitWhereUniqueInput | Prisma.HabitWhereUniqueInput[] + update?: Prisma.HabitUpdateWithWhereUniqueWithoutUserInput | Prisma.HabitUpdateWithWhereUniqueWithoutUserInput[] + updateMany?: Prisma.HabitUpdateManyWithWhereWithoutUserInput | Prisma.HabitUpdateManyWithWhereWithoutUserInput[] + deleteMany?: Prisma.HabitScalarWhereInput | Prisma.HabitScalarWhereInput[] +} + +export type HabitUncheckedUpdateManyWithoutUserNestedInput = { + create?: Prisma.XOR | Prisma.HabitCreateWithoutUserInput[] | Prisma.HabitUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.HabitCreateOrConnectWithoutUserInput | Prisma.HabitCreateOrConnectWithoutUserInput[] + upsert?: Prisma.HabitUpsertWithWhereUniqueWithoutUserInput | Prisma.HabitUpsertWithWhereUniqueWithoutUserInput[] + createMany?: Prisma.HabitCreateManyUserInputEnvelope + set?: Prisma.HabitWhereUniqueInput | Prisma.HabitWhereUniqueInput[] + disconnect?: Prisma.HabitWhereUniqueInput | Prisma.HabitWhereUniqueInput[] + delete?: Prisma.HabitWhereUniqueInput | Prisma.HabitWhereUniqueInput[] + connect?: Prisma.HabitWhereUniqueInput | Prisma.HabitWhereUniqueInput[] + update?: Prisma.HabitUpdateWithWhereUniqueWithoutUserInput | Prisma.HabitUpdateWithWhereUniqueWithoutUserInput[] + updateMany?: Prisma.HabitUpdateManyWithWhereWithoutUserInput | Prisma.HabitUpdateManyWithWhereWithoutUserInput[] + deleteMany?: Prisma.HabitScalarWhereInput | Prisma.HabitScalarWhereInput[] +} + +export type HabitCreateNestedOneWithoutRecordsInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.HabitCreateOrConnectWithoutRecordsInput + connect?: Prisma.HabitWhereUniqueInput +} + +export type HabitUpdateOneRequiredWithoutRecordsNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.HabitCreateOrConnectWithoutRecordsInput + upsert?: Prisma.HabitUpsertWithoutRecordsInput + connect?: Prisma.HabitWhereUniqueInput + update?: Prisma.XOR, Prisma.HabitUncheckedUpdateWithoutRecordsInput> +} + +export type HabitCreateWithoutUserInput = { + name: string + period?: string + target?: number + color?: string + icon?: string + note?: string + archived?: boolean + sortOrder?: number + createdAt?: Date | string + updatedAt?: Date | string + records?: Prisma.CheckInCreateNestedManyWithoutHabitInput +} + +export type HabitUncheckedCreateWithoutUserInput = { + id?: number + name: string + period?: string + target?: number + color?: string + icon?: string + note?: string + archived?: boolean + sortOrder?: number + createdAt?: Date | string + updatedAt?: Date | string + records?: Prisma.CheckInUncheckedCreateNestedManyWithoutHabitInput +} + +export type HabitCreateOrConnectWithoutUserInput = { + where: Prisma.HabitWhereUniqueInput + create: Prisma.XOR +} + +export type HabitCreateManyUserInputEnvelope = { + data: Prisma.HabitCreateManyUserInput | Prisma.HabitCreateManyUserInput[] + skipDuplicates?: boolean +} + +export type HabitUpsertWithWhereUniqueWithoutUserInput = { + where: Prisma.HabitWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type HabitUpdateWithWhereUniqueWithoutUserInput = { + where: Prisma.HabitWhereUniqueInput + data: Prisma.XOR +} + +export type HabitUpdateManyWithWhereWithoutUserInput = { + where: Prisma.HabitScalarWhereInput + data: Prisma.XOR +} + +export type HabitScalarWhereInput = { + AND?: Prisma.HabitScalarWhereInput | Prisma.HabitScalarWhereInput[] + OR?: Prisma.HabitScalarWhereInput[] + NOT?: Prisma.HabitScalarWhereInput | Prisma.HabitScalarWhereInput[] + id?: Prisma.IntFilter<"Habit"> | number + userId?: Prisma.IntFilter<"Habit"> | number + name?: Prisma.StringFilter<"Habit"> | string + period?: Prisma.StringFilter<"Habit"> | string + target?: Prisma.IntFilter<"Habit"> | number + color?: Prisma.StringFilter<"Habit"> | string + icon?: Prisma.StringFilter<"Habit"> | string + note?: Prisma.StringFilter<"Habit"> | string + archived?: Prisma.BoolFilter<"Habit"> | boolean + sortOrder?: Prisma.IntFilter<"Habit"> | number + createdAt?: Prisma.DateTimeFilter<"Habit"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"Habit"> | Date | string +} + +export type HabitCreateWithoutRecordsInput = { + name: string + period?: string + target?: number + color?: string + icon?: string + note?: string + archived?: boolean + sortOrder?: number + createdAt?: Date | string + updatedAt?: Date | string + user: Prisma.UserCreateNestedOneWithoutHabitsInput +} + +export type HabitUncheckedCreateWithoutRecordsInput = { + id?: number + userId: number + name: string + period?: string + target?: number + color?: string + icon?: string + note?: string + archived?: boolean + sortOrder?: number + createdAt?: Date | string + updatedAt?: Date | string +} + +export type HabitCreateOrConnectWithoutRecordsInput = { + where: Prisma.HabitWhereUniqueInput + create: Prisma.XOR +} + +export type HabitUpsertWithoutRecordsInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.HabitWhereInput +} + +export type HabitUpdateToOneWithWhereWithoutRecordsInput = { + where?: Prisma.HabitWhereInput + data: Prisma.XOR +} + +export type HabitUpdateWithoutRecordsInput = { + name?: Prisma.StringFieldUpdateOperationsInput | string + period?: Prisma.StringFieldUpdateOperationsInput | string + target?: Prisma.IntFieldUpdateOperationsInput | number + color?: Prisma.StringFieldUpdateOperationsInput | string + icon?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + user?: Prisma.UserUpdateOneRequiredWithoutHabitsNestedInput +} + +export type HabitUncheckedUpdateWithoutRecordsInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string + period?: Prisma.StringFieldUpdateOperationsInput | string + target?: Prisma.IntFieldUpdateOperationsInput | number + color?: Prisma.StringFieldUpdateOperationsInput | string + icon?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type HabitCreateManyUserInput = { + id?: number + name: string + period?: string + target?: number + color?: string + icon?: string + note?: string + archived?: boolean + sortOrder?: number + createdAt?: Date | string + updatedAt?: Date | string +} + +export type HabitUpdateWithoutUserInput = { + name?: Prisma.StringFieldUpdateOperationsInput | string + period?: Prisma.StringFieldUpdateOperationsInput | string + target?: Prisma.IntFieldUpdateOperationsInput | number + color?: Prisma.StringFieldUpdateOperationsInput | string + icon?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + records?: Prisma.CheckInUpdateManyWithoutHabitNestedInput +} + +export type HabitUncheckedUpdateWithoutUserInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string + period?: Prisma.StringFieldUpdateOperationsInput | string + target?: Prisma.IntFieldUpdateOperationsInput | number + color?: Prisma.StringFieldUpdateOperationsInput | string + icon?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + records?: Prisma.CheckInUncheckedUpdateManyWithoutHabitNestedInput +} + +export type HabitUncheckedUpdateManyWithoutUserInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string + period?: Prisma.StringFieldUpdateOperationsInput | string + target?: Prisma.IntFieldUpdateOperationsInput | number + color?: Prisma.StringFieldUpdateOperationsInput | string + icon?: Prisma.StringFieldUpdateOperationsInput | string + note?: Prisma.StringFieldUpdateOperationsInput | string + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + + +/** + * Count Type HabitCountOutputType + */ + +export type HabitCountOutputType = { + records: number +} + +export type HabitCountOutputTypeSelect = { + records?: boolean | HabitCountOutputTypeCountRecordsArgs +} + +/** + * HabitCountOutputType without action + */ +export type HabitCountOutputTypeDefaultArgs = { + /** + * Select specific fields to fetch from the HabitCountOutputType + */ + select?: Prisma.HabitCountOutputTypeSelect | null +} + +/** + * HabitCountOutputType without action + */ +export type HabitCountOutputTypeCountRecordsArgs = { + where?: Prisma.CheckInWhereInput +} + + +export type HabitSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + userId?: boolean + name?: boolean + period?: boolean + target?: boolean + color?: boolean + icon?: boolean + note?: boolean + archived?: boolean + sortOrder?: boolean + createdAt?: boolean + updatedAt?: boolean + user?: boolean | Prisma.UserDefaultArgs + records?: boolean | Prisma.Habit$recordsArgs + _count?: boolean | Prisma.HabitCountOutputTypeDefaultArgs +}, ExtArgs["result"]["habit"]> + +export type HabitSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + userId?: boolean + name?: boolean + period?: boolean + target?: boolean + color?: boolean + icon?: boolean + note?: boolean + archived?: boolean + sortOrder?: boolean + createdAt?: boolean + updatedAt?: boolean + user?: boolean | Prisma.UserDefaultArgs +}, ExtArgs["result"]["habit"]> + +export type HabitSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + userId?: boolean + name?: boolean + period?: boolean + target?: boolean + color?: boolean + icon?: boolean + note?: boolean + archived?: boolean + sortOrder?: boolean + createdAt?: boolean + updatedAt?: boolean + user?: boolean | Prisma.UserDefaultArgs +}, ExtArgs["result"]["habit"]> + +export type HabitSelectScalar = { + id?: boolean + userId?: boolean + name?: boolean + period?: boolean + target?: boolean + color?: boolean + icon?: boolean + note?: boolean + archived?: boolean + sortOrder?: boolean + createdAt?: boolean + updatedAt?: boolean +} + +export type HabitOmit = runtime.Types.Extensions.GetOmit<"id" | "userId" | "name" | "period" | "target" | "color" | "icon" | "note" | "archived" | "sortOrder" | "createdAt" | "updatedAt", ExtArgs["result"]["habit"]> +export type HabitInclude = { + user?: boolean | Prisma.UserDefaultArgs + records?: boolean | Prisma.Habit$recordsArgs + _count?: boolean | Prisma.HabitCountOutputTypeDefaultArgs +} +export type HabitIncludeCreateManyAndReturn = { + user?: boolean | Prisma.UserDefaultArgs +} +export type HabitIncludeUpdateManyAndReturn = { + user?: boolean | Prisma.UserDefaultArgs +} + +export type $HabitPayload = { + name: "Habit" + objects: { + user: Prisma.$UserPayload + records: Prisma.$CheckInPayload[] + } + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: number + userId: number + name: string + period: string + target: number + color: string + icon: string + note: string + archived: boolean + sortOrder: number + createdAt: Date + updatedAt: Date + }, ExtArgs["result"]["habit"]> + composites: {} +} + +export type HabitGetPayload = runtime.Types.Result.GetResult + +export type HabitCountArgs = + Omit & { + select?: HabitCountAggregateInputType | true + } + +export interface HabitDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['Habit'], meta: { name: 'Habit' } } + /** + * Find zero or one Habit that matches the filter. + * @param {HabitFindUniqueArgs} args - Arguments to find a Habit + * @example + * // Get one Habit + * const habit = await prisma.habit.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__HabitClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one Habit that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {HabitFindUniqueOrThrowArgs} args - Arguments to find a Habit + * @example + * // Get one Habit + * const habit = await prisma.habit.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__HabitClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first Habit that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {HabitFindFirstArgs} args - Arguments to find a Habit + * @example + * // Get one Habit + * const habit = await prisma.habit.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__HabitClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first Habit that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {HabitFindFirstOrThrowArgs} args - Arguments to find a Habit + * @example + * // Get one Habit + * const habit = await prisma.habit.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__HabitClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more Habits that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {HabitFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all Habits + * const habits = await prisma.habit.findMany() + * + * // Get first 10 Habits + * const habits = await prisma.habit.findMany({ take: 10 }) + * + * // Only select the `id` + * const habitWithIdOnly = await prisma.habit.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a Habit. + * @param {HabitCreateArgs} args - Arguments to create a Habit. + * @example + * // Create one Habit + * const Habit = await prisma.habit.create({ + * data: { + * // ... data to create a Habit + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__HabitClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many Habits. + * @param {HabitCreateManyArgs} args - Arguments to create many Habits. + * @example + * // Create many Habits + * const habit = await prisma.habit.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many Habits and returns the data saved in the database. + * @param {HabitCreateManyAndReturnArgs} args - Arguments to create many Habits. + * @example + * // Create many Habits + * const habit = await prisma.habit.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many Habits and only return the `id` + * const habitWithIdOnly = await prisma.habit.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a Habit. + * @param {HabitDeleteArgs} args - Arguments to delete one Habit. + * @example + * // Delete one Habit + * const Habit = await prisma.habit.delete({ + * where: { + * // ... filter to delete one Habit + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__HabitClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one Habit. + * @param {HabitUpdateArgs} args - Arguments to update one Habit. + * @example + * // Update one Habit + * const habit = await prisma.habit.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__HabitClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more Habits. + * @param {HabitDeleteManyArgs} args - Arguments to filter Habits to delete. + * @example + * // Delete a few Habits + * const { count } = await prisma.habit.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more Habits. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {HabitUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many Habits + * const habit = await prisma.habit.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more Habits and returns the data updated in the database. + * @param {HabitUpdateManyAndReturnArgs} args - Arguments to update many Habits. + * @example + * // Update many Habits + * const habit = await prisma.habit.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more Habits and only return the `id` + * const habitWithIdOnly = await prisma.habit.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one Habit. + * @param {HabitUpsertArgs} args - Arguments to update or create a Habit. + * @example + * // Update or create a Habit + * const habit = await prisma.habit.upsert({ + * create: { + * // ... data to create a Habit + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the Habit we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__HabitClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of Habits. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {HabitCountArgs} args - Arguments to filter Habits to count. + * @example + * // Count the number of Habits + * const count = await prisma.habit.count({ + * where: { + * // ... the filter for the Habits we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a Habit. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {HabitAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by Habit. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {HabitGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends HabitGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: HabitGroupByArgs['orderBy'] } + : { orderBy?: HabitGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetHabitGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the Habit model + */ +readonly fields: HabitFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for Habit. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__HabitClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + user = {}>(args?: Prisma.Subset>): Prisma.Prisma__UserClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + records = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the Habit model + */ +export interface HabitFieldRefs { + readonly id: Prisma.FieldRef<"Habit", 'Int'> + readonly userId: Prisma.FieldRef<"Habit", 'Int'> + readonly name: Prisma.FieldRef<"Habit", 'String'> + readonly period: Prisma.FieldRef<"Habit", 'String'> + readonly target: Prisma.FieldRef<"Habit", 'Int'> + readonly color: Prisma.FieldRef<"Habit", 'String'> + readonly icon: Prisma.FieldRef<"Habit", 'String'> + readonly note: Prisma.FieldRef<"Habit", 'String'> + readonly archived: Prisma.FieldRef<"Habit", 'Boolean'> + readonly sortOrder: Prisma.FieldRef<"Habit", 'Int'> + readonly createdAt: Prisma.FieldRef<"Habit", 'DateTime'> + readonly updatedAt: Prisma.FieldRef<"Habit", 'DateTime'> +} + + +// Custom InputTypes +/** + * Habit findUnique + */ +export type HabitFindUniqueArgs = { + /** + * Select specific fields to fetch from the Habit + */ + select?: Prisma.HabitSelect | null + /** + * Omit specific fields from the Habit + */ + omit?: Prisma.HabitOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.HabitInclude | null + /** + * Filter, which Habit to fetch. + */ + where: Prisma.HabitWhereUniqueInput +} + +/** + * Habit findUniqueOrThrow + */ +export type HabitFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the Habit + */ + select?: Prisma.HabitSelect | null + /** + * Omit specific fields from the Habit + */ + omit?: Prisma.HabitOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.HabitInclude | null + /** + * Filter, which Habit to fetch. + */ + where: Prisma.HabitWhereUniqueInput +} + +/** + * Habit findFirst + */ +export type HabitFindFirstArgs = { + /** + * Select specific fields to fetch from the Habit + */ + select?: Prisma.HabitSelect | null + /** + * Omit specific fields from the Habit + */ + omit?: Prisma.HabitOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.HabitInclude | null + /** + * Filter, which Habit to fetch. + */ + where?: Prisma.HabitWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Habits to fetch. + */ + orderBy?: Prisma.HabitOrderByWithRelationInput | Prisma.HabitOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for Habits. + */ + cursor?: Prisma.HabitWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Habits from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Habits. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Habits. + */ + distinct?: Prisma.HabitScalarFieldEnum | Prisma.HabitScalarFieldEnum[] +} + +/** + * Habit findFirstOrThrow + */ +export type HabitFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the Habit + */ + select?: Prisma.HabitSelect | null + /** + * Omit specific fields from the Habit + */ + omit?: Prisma.HabitOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.HabitInclude | null + /** + * Filter, which Habit to fetch. + */ + where?: Prisma.HabitWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Habits to fetch. + */ + orderBy?: Prisma.HabitOrderByWithRelationInput | Prisma.HabitOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for Habits. + */ + cursor?: Prisma.HabitWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Habits from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Habits. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Habits. + */ + distinct?: Prisma.HabitScalarFieldEnum | Prisma.HabitScalarFieldEnum[] +} + +/** + * Habit findMany + */ +export type HabitFindManyArgs = { + /** + * Select specific fields to fetch from the Habit + */ + select?: Prisma.HabitSelect | null + /** + * Omit specific fields from the Habit + */ + omit?: Prisma.HabitOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.HabitInclude | null + /** + * Filter, which Habits to fetch. + */ + where?: Prisma.HabitWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Habits to fetch. + */ + orderBy?: Prisma.HabitOrderByWithRelationInput | Prisma.HabitOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing Habits. + */ + cursor?: Prisma.HabitWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Habits from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Habits. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Habits. + */ + distinct?: Prisma.HabitScalarFieldEnum | Prisma.HabitScalarFieldEnum[] +} + +/** + * Habit create + */ +export type HabitCreateArgs = { + /** + * Select specific fields to fetch from the Habit + */ + select?: Prisma.HabitSelect | null + /** + * Omit specific fields from the Habit + */ + omit?: Prisma.HabitOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.HabitInclude | null + /** + * The data needed to create a Habit. + */ + data: Prisma.XOR +} + +/** + * Habit createMany + */ +export type HabitCreateManyArgs = { + /** + * The data used to create many Habits. + */ + data: Prisma.HabitCreateManyInput | Prisma.HabitCreateManyInput[] + skipDuplicates?: boolean +} + +/** + * Habit createManyAndReturn + */ +export type HabitCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the Habit + */ + select?: Prisma.HabitSelectCreateManyAndReturn | null + /** + * Omit specific fields from the Habit + */ + omit?: Prisma.HabitOmit | null + /** + * The data used to create many Habits. + */ + data: Prisma.HabitCreateManyInput | Prisma.HabitCreateManyInput[] + skipDuplicates?: boolean + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.HabitIncludeCreateManyAndReturn | null +} + +/** + * Habit update + */ +export type HabitUpdateArgs = { + /** + * Select specific fields to fetch from the Habit + */ + select?: Prisma.HabitSelect | null + /** + * Omit specific fields from the Habit + */ + omit?: Prisma.HabitOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.HabitInclude | null + /** + * The data needed to update a Habit. + */ + data: Prisma.XOR + /** + * Choose, which Habit to update. + */ + where: Prisma.HabitWhereUniqueInput +} + +/** + * Habit updateMany + */ +export type HabitUpdateManyArgs = { + /** + * The data used to update Habits. + */ + data: Prisma.XOR + /** + * Filter which Habits to update + */ + where?: Prisma.HabitWhereInput + /** + * Limit how many Habits to update. + */ + limit?: number +} + +/** + * Habit updateManyAndReturn + */ +export type HabitUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the Habit + */ + select?: Prisma.HabitSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the Habit + */ + omit?: Prisma.HabitOmit | null + /** + * The data used to update Habits. + */ + data: Prisma.XOR + /** + * Filter which Habits to update + */ + where?: Prisma.HabitWhereInput + /** + * Limit how many Habits to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.HabitIncludeUpdateManyAndReturn | null +} + +/** + * Habit upsert + */ +export type HabitUpsertArgs = { + /** + * Select specific fields to fetch from the Habit + */ + select?: Prisma.HabitSelect | null + /** + * Omit specific fields from the Habit + */ + omit?: Prisma.HabitOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.HabitInclude | null + /** + * The filter to search for the Habit to update in case it exists. + */ + where: Prisma.HabitWhereUniqueInput + /** + * In case the Habit found by the `where` argument doesn't exist, create a new Habit with this data. + */ + create: Prisma.XOR + /** + * In case the Habit was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * Habit delete + */ +export type HabitDeleteArgs = { + /** + * Select specific fields to fetch from the Habit + */ + select?: Prisma.HabitSelect | null + /** + * Omit specific fields from the Habit + */ + omit?: Prisma.HabitOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.HabitInclude | null + /** + * Filter which Habit to delete. + */ + where: Prisma.HabitWhereUniqueInput +} + +/** + * Habit deleteMany + */ +export type HabitDeleteManyArgs = { + /** + * Filter which Habits to delete + */ + where?: Prisma.HabitWhereInput + /** + * Limit how many Habits to delete. + */ + limit?: number +} + +/** + * Habit.records + */ +export type Habit$recordsArgs = { + /** + * Select specific fields to fetch from the CheckIn + */ + select?: Prisma.CheckInSelect | null + /** + * Omit specific fields from the CheckIn + */ + omit?: Prisma.CheckInOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckInInclude | null + where?: Prisma.CheckInWhereInput + orderBy?: Prisma.CheckInOrderByWithRelationInput | Prisma.CheckInOrderByWithRelationInput[] + cursor?: Prisma.CheckInWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.CheckInScalarFieldEnum | Prisma.CheckInScalarFieldEnum[] +} + +/** + * Habit without action + */ +export type HabitDefaultArgs = { + /** + * Select specific fields to fetch from the Habit + */ + select?: Prisma.HabitSelect | null + /** + * Omit specific fields from the Habit + */ + omit?: Prisma.HabitOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.HabitInclude | null +} diff --git a/generated/prisma/models/Indicator.ts b/generated/prisma/models/Indicator.ts index 07a1e90..55d4a94 100644 --- a/generated/prisma/models/Indicator.ts +++ b/generated/prisma/models/Indicator.ts @@ -271,7 +271,6 @@ export type IndicatorOrderByWithRelationInput = { createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder points?: Prisma.IndicatorPointOrderByRelationAggregateInput - _relevance?: Prisma.IndicatorOrderByRelevanceInput } export type IndicatorWhereUniqueInput = Prisma.AtLeast<{ @@ -417,12 +416,6 @@ export type IndicatorUncheckedUpdateManyInput = { updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string } -export type IndicatorOrderByRelevanceInput = { - fields: Prisma.IndicatorOrderByRelevanceFieldEnum | Prisma.IndicatorOrderByRelevanceFieldEnum[] - sort: Prisma.SortOrder - search: string -} - export type IndicatorCountOrderByAggregateInput = { id?: Prisma.SortOrder name?: Prisma.SortOrder @@ -605,7 +598,31 @@ export type IndicatorSelect }, ExtArgs["result"]["indicator"]> +export type IndicatorSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + name?: boolean + groupName?: boolean + title?: boolean + unit?: boolean + source?: boolean + frequency?: boolean + hidden?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["indicator"]> +export type IndicatorSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + name?: boolean + groupName?: boolean + title?: boolean + unit?: boolean + source?: boolean + frequency?: boolean + hidden?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["indicator"]> export type IndicatorSelectScalar = { id?: boolean @@ -625,6 +642,8 @@ export type IndicatorInclude _count?: boolean | Prisma.IndicatorCountOutputTypeDefaultArgs } +export type IndicatorIncludeCreateManyAndReturn = {} +export type IndicatorIncludeUpdateManyAndReturn = {} export type $IndicatorPayload = { name: "Indicator" @@ -759,6 +778,30 @@ export interface IndicatorDelegate(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Create many Indicators and returns the data saved in the database. + * @param {IndicatorCreateManyAndReturnArgs} args - Arguments to create many Indicators. + * @example + * // Create many Indicators + * const indicator = await prisma.indicator.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many Indicators and only return the `id` + * const indicatorWithIdOnly = await prisma.indicator.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + /** * Delete a Indicator. * @param {IndicatorDeleteArgs} args - Arguments to delete one Indicator. @@ -823,6 +866,36 @@ export interface IndicatorDelegate(args: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Update zero or more Indicators and returns the data updated in the database. + * @param {IndicatorUpdateManyAndReturnArgs} args - Arguments to update many Indicators. + * @example + * // Update many Indicators + * const indicator = await prisma.indicator.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more Indicators and only return the `id` + * const indicatorWithIdOnly = await prisma.indicator.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + /** * Create or update one Indicator. * @param {IndicatorUpsertArgs} args - Arguments to update or create a Indicator. @@ -1259,6 +1332,25 @@ export type IndicatorCreateManyArgs = { + /** + * Select specific fields to fetch from the Indicator + */ + select?: Prisma.IndicatorSelectCreateManyAndReturn | null + /** + * Omit specific fields from the Indicator + */ + omit?: Prisma.IndicatorOmit | null + /** + * The data used to create many Indicators. + */ + data: Prisma.IndicatorCreateManyInput | Prisma.IndicatorCreateManyInput[] + skipDuplicates?: boolean +} + /** * Indicator update */ @@ -1303,6 +1395,32 @@ export type IndicatorUpdateManyArgs = { + /** + * Select specific fields to fetch from the Indicator + */ + select?: Prisma.IndicatorSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the Indicator + */ + omit?: Prisma.IndicatorOmit | null + /** + * The data used to update Indicators. + */ + data: Prisma.XOR + /** + * Filter which Indicators to update + */ + where?: Prisma.IndicatorWhereInput + /** + * Limit how many Indicators to update. + */ + limit?: number +} + /** * Indicator upsert */ diff --git a/generated/prisma/models/IndicatorPoint.ts b/generated/prisma/models/IndicatorPoint.ts index 45c9fda..c2eb452 100644 --- a/generated/prisma/models/IndicatorPoint.ts +++ b/generated/prisma/models/IndicatorPoint.ts @@ -243,7 +243,6 @@ export type IndicatorPointOrderByWithRelationInput = { createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder indicator?: Prisma.IndicatorOrderByWithRelationInput - _relevance?: Prisma.IndicatorPointOrderByRelevanceInput } export type IndicatorPointWhereUniqueInput = Prisma.AtLeast<{ @@ -355,12 +354,6 @@ export type IndicatorPointOrderByRelationAggregateInput = { _count?: Prisma.SortOrder } -export type IndicatorPointOrderByRelevanceInput = { - fields: Prisma.IndicatorPointOrderByRelevanceFieldEnum | Prisma.IndicatorPointOrderByRelevanceFieldEnum[] - sort: Prisma.SortOrder - search: string -} - export type IndicatorPointIndicatorIdPeriodCompoundUniqueInput = { indicatorId: number period: string @@ -551,7 +544,25 @@ export type IndicatorPointSelect }, ExtArgs["result"]["indicatorPoint"]> +export type IndicatorPointSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + indicatorId?: boolean + period?: boolean + value?: boolean + createdAt?: boolean + updatedAt?: boolean + indicator?: boolean | Prisma.IndicatorDefaultArgs +}, ExtArgs["result"]["indicatorPoint"]> +export type IndicatorPointSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + indicatorId?: boolean + period?: boolean + value?: boolean + createdAt?: boolean + updatedAt?: boolean + indicator?: boolean | Prisma.IndicatorDefaultArgs +}, ExtArgs["result"]["indicatorPoint"]> export type IndicatorPointSelectScalar = { id?: boolean @@ -566,6 +577,12 @@ export type IndicatorPointOmit = { indicator?: boolean | Prisma.IndicatorDefaultArgs } +export type IndicatorPointIncludeCreateManyAndReturn = { + indicator?: boolean | Prisma.IndicatorDefaultArgs +} +export type IndicatorPointIncludeUpdateManyAndReturn = { + indicator?: boolean | Prisma.IndicatorDefaultArgs +} export type $IndicatorPointPayload = { name: "IndicatorPoint" @@ -696,6 +713,30 @@ export interface IndicatorPointDelegate(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Create many IndicatorPoints and returns the data saved in the database. + * @param {IndicatorPointCreateManyAndReturnArgs} args - Arguments to create many IndicatorPoints. + * @example + * // Create many IndicatorPoints + * const indicatorPoint = await prisma.indicatorPoint.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many IndicatorPoints and only return the `id` + * const indicatorPointWithIdOnly = await prisma.indicatorPoint.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + /** * Delete a IndicatorPoint. * @param {IndicatorPointDeleteArgs} args - Arguments to delete one IndicatorPoint. @@ -760,6 +801,36 @@ export interface IndicatorPointDelegate(args: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Update zero or more IndicatorPoints and returns the data updated in the database. + * @param {IndicatorPointUpdateManyAndReturnArgs} args - Arguments to update many IndicatorPoints. + * @example + * // Update many IndicatorPoints + * const indicatorPoint = await prisma.indicatorPoint.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more IndicatorPoints and only return the `id` + * const indicatorPointWithIdOnly = await prisma.indicatorPoint.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + /** * Create or update one IndicatorPoint. * @param {IndicatorPointUpsertArgs} args - Arguments to update or create a IndicatorPoint. @@ -1192,6 +1263,29 @@ export type IndicatorPointCreateManyArgs = { + /** + * Select specific fields to fetch from the IndicatorPoint + */ + select?: Prisma.IndicatorPointSelectCreateManyAndReturn | null + /** + * Omit specific fields from the IndicatorPoint + */ + omit?: Prisma.IndicatorPointOmit | null + /** + * The data used to create many IndicatorPoints. + */ + data: Prisma.IndicatorPointCreateManyInput | Prisma.IndicatorPointCreateManyInput[] + skipDuplicates?: boolean + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.IndicatorPointIncludeCreateManyAndReturn | null +} + /** * IndicatorPoint update */ @@ -1236,6 +1330,36 @@ export type IndicatorPointUpdateManyArgs = { + /** + * Select specific fields to fetch from the IndicatorPoint + */ + select?: Prisma.IndicatorPointSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the IndicatorPoint + */ + omit?: Prisma.IndicatorPointOmit | null + /** + * The data used to update IndicatorPoints. + */ + data: Prisma.XOR + /** + * Filter which IndicatorPoints to update + */ + where?: Prisma.IndicatorPointWhereInput + /** + * Limit how many IndicatorPoints to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.IndicatorPointIncludeUpdateManyAndReturn | null +} + /** * IndicatorPoint upsert */ diff --git a/generated/prisma/models/MihomeAccount.ts b/generated/prisma/models/MihomeAccount.ts index e0452c3..8b9b3bf 100644 --- a/generated/prisma/models/MihomeAccount.ts +++ b/generated/prisma/models/MihomeAccount.ts @@ -233,7 +233,6 @@ export type MihomeAccountOrderByWithRelationInput = { status?: Prisma.SortOrder lastError?: Prisma.SortOrder updatedAt?: Prisma.SortOrder - _relevance?: Prisma.MihomeAccountOrderByRelevanceInput } export type MihomeAccountWhereUniqueInput = Prisma.AtLeast<{ @@ -334,12 +333,6 @@ export type MihomeAccountUncheckedUpdateManyInput = { updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string } -export type MihomeAccountOrderByRelevanceInput = { - fields: Prisma.MihomeAccountOrderByRelevanceFieldEnum | Prisma.MihomeAccountOrderByRelevanceFieldEnum[] - sort: Prisma.SortOrder - search: string -} - export type MihomeAccountCountOrderByAggregateInput = { id?: Prisma.SortOrder key?: Prisma.SortOrder @@ -386,7 +379,23 @@ export type MihomeAccountSelect +export type MihomeAccountSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + key?: boolean + userId?: boolean + status?: boolean + lastError?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["mihomeAccount"]> +export type MihomeAccountSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + key?: boolean + userId?: boolean + status?: boolean + lastError?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["mihomeAccount"]> export type MihomeAccountSelectScalar = { id?: boolean @@ -526,6 +535,30 @@ export interface MihomeAccountDelegate(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Create many MihomeAccounts and returns the data saved in the database. + * @param {MihomeAccountCreateManyAndReturnArgs} args - Arguments to create many MihomeAccounts. + * @example + * // Create many MihomeAccounts + * const mihomeAccount = await prisma.mihomeAccount.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many MihomeAccounts and only return the `id` + * const mihomeAccountWithIdOnly = await prisma.mihomeAccount.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + /** * Delete a MihomeAccount. * @param {MihomeAccountDeleteArgs} args - Arguments to delete one MihomeAccount. @@ -590,6 +623,36 @@ export interface MihomeAccountDelegate(args: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Update zero or more MihomeAccounts and returns the data updated in the database. + * @param {MihomeAccountUpdateManyAndReturnArgs} args - Arguments to update many MihomeAccounts. + * @example + * // Update many MihomeAccounts + * const mihomeAccount = await prisma.mihomeAccount.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more MihomeAccounts and only return the `id` + * const mihomeAccountWithIdOnly = await prisma.mihomeAccount.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + /** * Create or update one MihomeAccount. * @param {MihomeAccountUpsertArgs} args - Arguments to update or create a MihomeAccount. @@ -997,6 +1060,25 @@ export type MihomeAccountCreateManyArgs = { + /** + * Select specific fields to fetch from the MihomeAccount + */ + select?: Prisma.MihomeAccountSelectCreateManyAndReturn | null + /** + * Omit specific fields from the MihomeAccount + */ + omit?: Prisma.MihomeAccountOmit | null + /** + * The data used to create many MihomeAccounts. + */ + data: Prisma.MihomeAccountCreateManyInput | Prisma.MihomeAccountCreateManyInput[] + skipDuplicates?: boolean +} + /** * MihomeAccount update */ @@ -1037,6 +1119,32 @@ export type MihomeAccountUpdateManyArgs = { + /** + * Select specific fields to fetch from the MihomeAccount + */ + select?: Prisma.MihomeAccountSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the MihomeAccount + */ + omit?: Prisma.MihomeAccountOmit | null + /** + * The data used to update MihomeAccounts. + */ + data: Prisma.XOR + /** + * Filter which MihomeAccounts to update + */ + where?: Prisma.MihomeAccountWhereInput + /** + * Limit how many MihomeAccounts to update. + */ + limit?: number +} + /** * MihomeAccount upsert */ diff --git a/generated/prisma/models/MihomeDevice.ts b/generated/prisma/models/MihomeDevice.ts index 147f96b..1bc75d8 100644 --- a/generated/prisma/models/MihomeDevice.ts +++ b/generated/prisma/models/MihomeDevice.ts @@ -366,7 +366,6 @@ export type MihomeDeviceOrderByWithRelationInput = { home?: Prisma.MihomeHomeOrderByWithRelationInput samples?: Prisma.MihomeEnvSampleOrderByRelationAggregateInput hours?: Prisma.MihomeEnvHourOrderByRelationAggregateInput - _relevance?: Prisma.MihomeDeviceOrderByRelevanceInput } export type MihomeDeviceWhereUniqueInput = Prisma.AtLeast<{ @@ -597,12 +596,6 @@ export type MihomeDeviceOrderByRelationAggregateInput = { _count?: Prisma.SortOrder } -export type MihomeDeviceOrderByRelevanceInput = { - fields: Prisma.MihomeDeviceOrderByRelevanceFieldEnum | Prisma.MihomeDeviceOrderByRelevanceFieldEnum[] - sort: Prisma.SortOrder - search: string -} - export type MihomeDeviceCountOrderByAggregateInput = { id?: Prisma.SortOrder did?: Prisma.SortOrder @@ -1201,7 +1194,47 @@ export type MihomeDeviceSelect }, ExtArgs["result"]["mihomeDevice"]> +export type MihomeDeviceSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + did?: boolean + homeId?: boolean + model?: boolean + name?: boolean + alias?: boolean + tempSiid?: boolean + tempPiid?: boolean + humSiid?: boolean + humPiid?: boolean + battSiid?: boolean + battPiid?: boolean + battery?: boolean + batteryAt?: boolean + enabled?: boolean + lastSyncAt?: boolean + createdAt?: boolean + home?: boolean | Prisma.MihomeHomeDefaultArgs +}, ExtArgs["result"]["mihomeDevice"]> +export type MihomeDeviceSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + did?: boolean + homeId?: boolean + model?: boolean + name?: boolean + alias?: boolean + tempSiid?: boolean + tempPiid?: boolean + humSiid?: boolean + humPiid?: boolean + battSiid?: boolean + battPiid?: boolean + battery?: boolean + batteryAt?: boolean + enabled?: boolean + lastSyncAt?: boolean + createdAt?: boolean + home?: boolean | Prisma.MihomeHomeDefaultArgs +}, ExtArgs["result"]["mihomeDevice"]> export type MihomeDeviceSelectScalar = { id?: boolean @@ -1230,6 +1263,12 @@ export type MihomeDeviceInclude _count?: boolean | Prisma.MihomeDeviceCountOutputTypeDefaultArgs } +export type MihomeDeviceIncludeCreateManyAndReturn = { + home?: boolean | Prisma.MihomeHomeDefaultArgs +} +export type MihomeDeviceIncludeUpdateManyAndReturn = { + home?: boolean | Prisma.MihomeHomeDefaultArgs +} export type $MihomeDevicePayload = { name: "MihomeDevice" @@ -1373,6 +1412,30 @@ export interface MihomeDeviceDelegate(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Create many MihomeDevices and returns the data saved in the database. + * @param {MihomeDeviceCreateManyAndReturnArgs} args - Arguments to create many MihomeDevices. + * @example + * // Create many MihomeDevices + * const mihomeDevice = await prisma.mihomeDevice.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many MihomeDevices and only return the `id` + * const mihomeDeviceWithIdOnly = await prisma.mihomeDevice.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + /** * Delete a MihomeDevice. * @param {MihomeDeviceDeleteArgs} args - Arguments to delete one MihomeDevice. @@ -1437,6 +1500,36 @@ export interface MihomeDeviceDelegate(args: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Update zero or more MihomeDevices and returns the data updated in the database. + * @param {MihomeDeviceUpdateManyAndReturnArgs} args - Arguments to update many MihomeDevices. + * @example + * // Update many MihomeDevices + * const mihomeDevice = await prisma.mihomeDevice.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more MihomeDevices and only return the `id` + * const mihomeDeviceWithIdOnly = await prisma.mihomeDevice.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + /** * Create or update one MihomeDevice. * @param {MihomeDeviceUpsertArgs} args - Arguments to update or create a MihomeDevice. @@ -1882,6 +1975,29 @@ export type MihomeDeviceCreateManyArgs = { + /** + * Select specific fields to fetch from the MihomeDevice + */ + select?: Prisma.MihomeDeviceSelectCreateManyAndReturn | null + /** + * Omit specific fields from the MihomeDevice + */ + omit?: Prisma.MihomeDeviceOmit | null + /** + * The data used to create many MihomeDevices. + */ + data: Prisma.MihomeDeviceCreateManyInput | Prisma.MihomeDeviceCreateManyInput[] + skipDuplicates?: boolean + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.MihomeDeviceIncludeCreateManyAndReturn | null +} + /** * MihomeDevice update */ @@ -1926,6 +2042,36 @@ export type MihomeDeviceUpdateManyArgs = { + /** + * Select specific fields to fetch from the MihomeDevice + */ + select?: Prisma.MihomeDeviceSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the MihomeDevice + */ + omit?: Prisma.MihomeDeviceOmit | null + /** + * The data used to update MihomeDevices. + */ + data: Prisma.XOR + /** + * Filter which MihomeDevices to update + */ + where?: Prisma.MihomeDeviceWhereInput + /** + * Limit how many MihomeDevices to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.MihomeDeviceIncludeUpdateManyAndReturn | null +} + /** * MihomeDevice upsert */ diff --git a/generated/prisma/models/MihomeEnvHour.ts b/generated/prisma/models/MihomeEnvHour.ts index caf5ddd..d9369ad 100644 --- a/generated/prisma/models/MihomeEnvHour.ts +++ b/generated/prisma/models/MihomeEnvHour.ts @@ -566,7 +566,27 @@ export type MihomeEnvHourSelect }, ExtArgs["result"]["mihomeEnvHour"]> +export type MihomeEnvHourSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + deviceId?: boolean + hour?: boolean + temperatureAvg?: boolean + humidityAvg?: boolean + raw?: boolean + createdAt?: boolean + device?: boolean | Prisma.MihomeDeviceDefaultArgs +}, ExtArgs["result"]["mihomeEnvHour"]> +export type MihomeEnvHourSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + deviceId?: boolean + hour?: boolean + temperatureAvg?: boolean + humidityAvg?: boolean + raw?: boolean + createdAt?: boolean + device?: boolean | Prisma.MihomeDeviceDefaultArgs +}, ExtArgs["result"]["mihomeEnvHour"]> export type MihomeEnvHourSelectScalar = { id?: boolean @@ -582,6 +602,12 @@ export type MihomeEnvHourOmit = { device?: boolean | Prisma.MihomeDeviceDefaultArgs } +export type MihomeEnvHourIncludeCreateManyAndReturn = { + device?: boolean | Prisma.MihomeDeviceDefaultArgs +} +export type MihomeEnvHourIncludeUpdateManyAndReturn = { + device?: boolean | Prisma.MihomeDeviceDefaultArgs +} export type $MihomeEnvHourPayload = { name: "MihomeEnvHour" @@ -713,6 +739,30 @@ export interface MihomeEnvHourDelegate(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Create many MihomeEnvHours and returns the data saved in the database. + * @param {MihomeEnvHourCreateManyAndReturnArgs} args - Arguments to create many MihomeEnvHours. + * @example + * // Create many MihomeEnvHours + * const mihomeEnvHour = await prisma.mihomeEnvHour.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many MihomeEnvHours and only return the `id` + * const mihomeEnvHourWithIdOnly = await prisma.mihomeEnvHour.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + /** * Delete a MihomeEnvHour. * @param {MihomeEnvHourDeleteArgs} args - Arguments to delete one MihomeEnvHour. @@ -777,6 +827,36 @@ export interface MihomeEnvHourDelegate(args: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Update zero or more MihomeEnvHours and returns the data updated in the database. + * @param {MihomeEnvHourUpdateManyAndReturnArgs} args - Arguments to update many MihomeEnvHours. + * @example + * // Update many MihomeEnvHours + * const mihomeEnvHour = await prisma.mihomeEnvHour.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more MihomeEnvHours and only return the `id` + * const mihomeEnvHourWithIdOnly = await prisma.mihomeEnvHour.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + /** * Create or update one MihomeEnvHour. * @param {MihomeEnvHourUpsertArgs} args - Arguments to update or create a MihomeEnvHour. @@ -1210,6 +1290,29 @@ export type MihomeEnvHourCreateManyArgs = { + /** + * Select specific fields to fetch from the MihomeEnvHour + */ + select?: Prisma.MihomeEnvHourSelectCreateManyAndReturn | null + /** + * Omit specific fields from the MihomeEnvHour + */ + omit?: Prisma.MihomeEnvHourOmit | null + /** + * The data used to create many MihomeEnvHours. + */ + data: Prisma.MihomeEnvHourCreateManyInput | Prisma.MihomeEnvHourCreateManyInput[] + skipDuplicates?: boolean + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.MihomeEnvHourIncludeCreateManyAndReturn | null +} + /** * MihomeEnvHour update */ @@ -1254,6 +1357,36 @@ export type MihomeEnvHourUpdateManyArgs = { + /** + * Select specific fields to fetch from the MihomeEnvHour + */ + select?: Prisma.MihomeEnvHourSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the MihomeEnvHour + */ + omit?: Prisma.MihomeEnvHourOmit | null + /** + * The data used to update MihomeEnvHours. + */ + data: Prisma.XOR + /** + * Filter which MihomeEnvHours to update + */ + where?: Prisma.MihomeEnvHourWhereInput + /** + * Limit how many MihomeEnvHours to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.MihomeEnvHourIncludeUpdateManyAndReturn | null +} + /** * MihomeEnvHour upsert */ diff --git a/generated/prisma/models/MihomeEnvSample.ts b/generated/prisma/models/MihomeEnvSample.ts index 3d6f41c..84bdbb3 100644 --- a/generated/prisma/models/MihomeEnvSample.ts +++ b/generated/prisma/models/MihomeEnvSample.ts @@ -348,7 +348,6 @@ export type MihomeEnvSampleOrderByWithRelationInput = { raw?: Prisma.SortOrderInput | Prisma.SortOrder createdAt?: Prisma.SortOrder device?: Prisma.MihomeDeviceOrderByWithRelationInput - _relevance?: Prisma.MihomeEnvSampleOrderByRelevanceInput } export type MihomeEnvSampleWhereUniqueInput = Prisma.AtLeast<{ @@ -550,12 +549,6 @@ export type MihomeEnvSampleOrderByRelationAggregateInput = { _count?: Prisma.SortOrder } -export type MihomeEnvSampleOrderByRelevanceInput = { - fields: Prisma.MihomeEnvSampleOrderByRelevanceFieldEnum | Prisma.MihomeEnvSampleOrderByRelevanceFieldEnum[] - sort: Prisma.SortOrder - search: string -} - export type MihomeEnvSampleDeviceIdDateCompoundUniqueInput = { deviceId: number date: Date | string @@ -857,7 +850,43 @@ export type MihomeEnvSampleSelect }, ExtArgs["result"]["mihomeEnvSample"]> +export type MihomeEnvSampleSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + deviceId?: boolean + date?: boolean + temperature?: boolean + humidity?: boolean + temperatureAvg?: boolean + humidityAvg?: boolean + temperatureMin?: boolean + temperatureMax?: boolean + humidityMin?: boolean + humidityMax?: boolean + origin?: boolean + sampledAt?: boolean + raw?: boolean + createdAt?: boolean + device?: boolean | Prisma.MihomeDeviceDefaultArgs +}, ExtArgs["result"]["mihomeEnvSample"]> +export type MihomeEnvSampleSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + deviceId?: boolean + date?: boolean + temperature?: boolean + humidity?: boolean + temperatureAvg?: boolean + humidityAvg?: boolean + temperatureMin?: boolean + temperatureMax?: boolean + humidityMin?: boolean + humidityMax?: boolean + origin?: boolean + sampledAt?: boolean + raw?: boolean + createdAt?: boolean + device?: boolean | Prisma.MihomeDeviceDefaultArgs +}, ExtArgs["result"]["mihomeEnvSample"]> export type MihomeEnvSampleSelectScalar = { id?: boolean @@ -881,6 +910,12 @@ export type MihomeEnvSampleOmit = { device?: boolean | Prisma.MihomeDeviceDefaultArgs } +export type MihomeEnvSampleIncludeCreateManyAndReturn = { + device?: boolean | Prisma.MihomeDeviceDefaultArgs +} +export type MihomeEnvSampleIncludeUpdateManyAndReturn = { + device?: boolean | Prisma.MihomeDeviceDefaultArgs +} export type $MihomeEnvSamplePayload = { name: "MihomeEnvSample" @@ -1020,6 +1055,30 @@ export interface MihomeEnvSampleDelegate(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Create many MihomeEnvSamples and returns the data saved in the database. + * @param {MihomeEnvSampleCreateManyAndReturnArgs} args - Arguments to create many MihomeEnvSamples. + * @example + * // Create many MihomeEnvSamples + * const mihomeEnvSample = await prisma.mihomeEnvSample.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many MihomeEnvSamples and only return the `id` + * const mihomeEnvSampleWithIdOnly = await prisma.mihomeEnvSample.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + /** * Delete a MihomeEnvSample. * @param {MihomeEnvSampleDeleteArgs} args - Arguments to delete one MihomeEnvSample. @@ -1084,6 +1143,36 @@ export interface MihomeEnvSampleDelegate(args: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Update zero or more MihomeEnvSamples and returns the data updated in the database. + * @param {MihomeEnvSampleUpdateManyAndReturnArgs} args - Arguments to update many MihomeEnvSamples. + * @example + * // Update many MihomeEnvSamples + * const mihomeEnvSample = await prisma.mihomeEnvSample.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more MihomeEnvSamples and only return the `id` + * const mihomeEnvSampleWithIdOnly = await prisma.mihomeEnvSample.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + /** * Create or update one MihomeEnvSample. * @param {MihomeEnvSampleUpsertArgs} args - Arguments to update or create a MihomeEnvSample. @@ -1525,6 +1614,29 @@ export type MihomeEnvSampleCreateManyArgs = { + /** + * Select specific fields to fetch from the MihomeEnvSample + */ + select?: Prisma.MihomeEnvSampleSelectCreateManyAndReturn | null + /** + * Omit specific fields from the MihomeEnvSample + */ + omit?: Prisma.MihomeEnvSampleOmit | null + /** + * The data used to create many MihomeEnvSamples. + */ + data: Prisma.MihomeEnvSampleCreateManyInput | Prisma.MihomeEnvSampleCreateManyInput[] + skipDuplicates?: boolean + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.MihomeEnvSampleIncludeCreateManyAndReturn | null +} + /** * MihomeEnvSample update */ @@ -1569,6 +1681,36 @@ export type MihomeEnvSampleUpdateManyArgs = { + /** + * Select specific fields to fetch from the MihomeEnvSample + */ + select?: Prisma.MihomeEnvSampleSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the MihomeEnvSample + */ + omit?: Prisma.MihomeEnvSampleOmit | null + /** + * The data used to update MihomeEnvSamples. + */ + data: Prisma.XOR + /** + * Filter which MihomeEnvSamples to update + */ + where?: Prisma.MihomeEnvSampleWhereInput + /** + * Limit how many MihomeEnvSamples to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.MihomeEnvSampleIncludeUpdateManyAndReturn | null +} + /** * MihomeEnvSample upsert */ diff --git a/generated/prisma/models/MihomeHome.ts b/generated/prisma/models/MihomeHome.ts index 86b93c0..357258f 100644 --- a/generated/prisma/models/MihomeHome.ts +++ b/generated/prisma/models/MihomeHome.ts @@ -217,7 +217,6 @@ export type MihomeHomeOrderByWithRelationInput = { name?: Prisma.SortOrder createdAt?: Prisma.SortOrder devices?: Prisma.MihomeDeviceOrderByRelationAggregateInput - _relevance?: Prisma.MihomeHomeOrderByRelevanceInput } export type MihomeHomeWhereUniqueInput = Prisma.AtLeast<{ @@ -303,12 +302,6 @@ export type MihomeHomeUncheckedUpdateManyInput = { createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string } -export type MihomeHomeOrderByRelevanceInput = { - fields: Prisma.MihomeHomeOrderByRelevanceFieldEnum | Prisma.MihomeHomeOrderByRelevanceFieldEnum[] - sort: Prisma.SortOrder - search: string -} - export type MihomeHomeCountOrderByAggregateInput = { id?: Prisma.SortOrder homeId?: Prisma.SortOrder @@ -439,7 +432,19 @@ export type MihomeHomeSelect }, ExtArgs["result"]["mihomeHome"]> +export type MihomeHomeSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + homeId?: boolean + name?: boolean + createdAt?: boolean +}, ExtArgs["result"]["mihomeHome"]> +export type MihomeHomeSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + homeId?: boolean + name?: boolean + createdAt?: boolean +}, ExtArgs["result"]["mihomeHome"]> export type MihomeHomeSelectScalar = { id?: boolean @@ -453,6 +458,8 @@ export type MihomeHomeInclude _count?: boolean | Prisma.MihomeHomeCountOutputTypeDefaultArgs } +export type MihomeHomeIncludeCreateManyAndReturn = {} +export type MihomeHomeIncludeUpdateManyAndReturn = {} export type $MihomeHomePayload = { name: "MihomeHome" @@ -581,6 +588,30 @@ export interface MihomeHomeDelegate(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Create many MihomeHomes and returns the data saved in the database. + * @param {MihomeHomeCreateManyAndReturnArgs} args - Arguments to create many MihomeHomes. + * @example + * // Create many MihomeHomes + * const mihomeHome = await prisma.mihomeHome.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many MihomeHomes and only return the `id` + * const mihomeHomeWithIdOnly = await prisma.mihomeHome.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + /** * Delete a MihomeHome. * @param {MihomeHomeDeleteArgs} args - Arguments to delete one MihomeHome. @@ -645,6 +676,36 @@ export interface MihomeHomeDelegate(args: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Update zero or more MihomeHomes and returns the data updated in the database. + * @param {MihomeHomeUpdateManyAndReturnArgs} args - Arguments to update many MihomeHomes. + * @example + * // Update many MihomeHomes + * const mihomeHome = await prisma.mihomeHome.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more MihomeHomes and only return the `id` + * const mihomeHomeWithIdOnly = await prisma.mihomeHome.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + /** * Create or update one MihomeHome. * @param {MihomeHomeUpsertArgs} args - Arguments to update or create a MihomeHome. @@ -1075,6 +1136,25 @@ export type MihomeHomeCreateManyArgs = { + /** + * Select specific fields to fetch from the MihomeHome + */ + select?: Prisma.MihomeHomeSelectCreateManyAndReturn | null + /** + * Omit specific fields from the MihomeHome + */ + omit?: Prisma.MihomeHomeOmit | null + /** + * The data used to create many MihomeHomes. + */ + data: Prisma.MihomeHomeCreateManyInput | Prisma.MihomeHomeCreateManyInput[] + skipDuplicates?: boolean +} + /** * MihomeHome update */ @@ -1119,6 +1199,32 @@ export type MihomeHomeUpdateManyArgs = { + /** + * Select specific fields to fetch from the MihomeHome + */ + select?: Prisma.MihomeHomeSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the MihomeHome + */ + omit?: Prisma.MihomeHomeOmit | null + /** + * The data used to update MihomeHomes. + */ + data: Prisma.XOR + /** + * Filter which MihomeHomes to update + */ + where?: Prisma.MihomeHomeWhereInput + /** + * Limit how many MihomeHomes to update. + */ + limit?: number +} + /** * MihomeHome upsert */ diff --git a/generated/prisma/models/News.ts b/generated/prisma/models/News.ts index 371f28c..0e3e81e 100644 --- a/generated/prisma/models/News.ts +++ b/generated/prisma/models/News.ts @@ -264,7 +264,6 @@ export type NewsOrderByWithRelationInput = { publishedAt?: Prisma.SortOrder impactRank?: Prisma.SortOrder createdAt?: Prisma.SortOrder - _relevance?: Prisma.NewsOrderByRelevanceInput } export type NewsWhereUniqueInput = Prisma.AtLeast<{ @@ -396,12 +395,6 @@ export type NewsUncheckedUpdateManyInput = { createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string } -export type NewsOrderByRelevanceInput = { - fields: Prisma.NewsOrderByRelevanceFieldEnum | Prisma.NewsOrderByRelevanceFieldEnum[] - sort: Prisma.SortOrder - search: string -} - export type NewsTitlePublishedAtCompoundUniqueInput = { title: string publishedAt: Date | string @@ -471,7 +464,29 @@ export type NewsSelect +export type NewsSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + title?: boolean + summary?: boolean + content?: boolean + source?: boolean + url?: boolean + publishedAt?: boolean + impactRank?: boolean + createdAt?: boolean +}, ExtArgs["result"]["news"]> +export type NewsSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + title?: boolean + summary?: boolean + content?: boolean + source?: boolean + url?: boolean + publishedAt?: boolean + impactRank?: boolean + createdAt?: boolean +}, ExtArgs["result"]["news"]> export type NewsSelectScalar = { id?: boolean @@ -617,6 +632,30 @@ export interface NewsDelegate(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Create many News and returns the data saved in the database. + * @param {NewsCreateManyAndReturnArgs} args - Arguments to create many News. + * @example + * // Create many News + * const news = await prisma.news.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many News and only return the `id` + * const newsWithIdOnly = await prisma.news.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + /** * Delete a News. * @param {NewsDeleteArgs} args - Arguments to delete one News. @@ -681,6 +720,36 @@ export interface NewsDelegate(args: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Update zero or more News and returns the data updated in the database. + * @param {NewsUpdateManyAndReturnArgs} args - Arguments to update many News. + * @example + * // Update many News + * const news = await prisma.news.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more News and only return the `id` + * const newsWithIdOnly = await prisma.news.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + /** * Create or update one News. * @param {NewsUpsertArgs} args - Arguments to update or create a News. @@ -1091,6 +1160,25 @@ export type NewsCreateManyArgs = { + /** + * Select specific fields to fetch from the News + */ + select?: Prisma.NewsSelectCreateManyAndReturn | null + /** + * Omit specific fields from the News + */ + omit?: Prisma.NewsOmit | null + /** + * The data used to create many News. + */ + data: Prisma.NewsCreateManyInput | Prisma.NewsCreateManyInput[] + skipDuplicates?: boolean +} + /** * News update */ @@ -1131,6 +1219,32 @@ export type NewsUpdateManyArgs = { + /** + * Select specific fields to fetch from the News + */ + select?: Prisma.NewsSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the News + */ + omit?: Prisma.NewsOmit | null + /** + * The data used to update News. + */ + data: Prisma.XOR + /** + * Filter which News to update + */ + where?: Prisma.NewsWhereInput + /** + * Limit how many News to update. + */ + limit?: number +} + /** * News upsert */ diff --git a/generated/prisma/models/OilPriceHistory.ts b/generated/prisma/models/OilPriceHistory.ts index 0a2c329..c3d2204 100644 --- a/generated/prisma/models/OilPriceHistory.ts +++ b/generated/prisma/models/OilPriceHistory.ts @@ -246,7 +246,6 @@ export type OilPriceHistoryOrderByWithRelationInput = { price?: Prisma.SortOrder source?: Prisma.SortOrder createdAt?: Prisma.SortOrder - _relevance?: Prisma.OilPriceHistoryOrderByRelevanceInput } export type OilPriceHistoryWhereUniqueInput = Prisma.AtLeast<{ @@ -358,12 +357,6 @@ export type OilPriceHistoryUncheckedUpdateManyInput = { createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string } -export type OilPriceHistoryOrderByRelevanceInput = { - fields: Prisma.OilPriceHistoryOrderByRelevanceFieldEnum | Prisma.OilPriceHistoryOrderByRelevanceFieldEnum[] - sort: Prisma.SortOrder - search: string -} - export type OilPriceHistoryDateProvinceFuelCompoundUniqueInput = { date: Date | string province: string @@ -422,7 +415,25 @@ export type OilPriceHistorySelect +export type OilPriceHistorySelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + date?: boolean + province?: boolean + fuel?: boolean + price?: boolean + source?: boolean + createdAt?: boolean +}, ExtArgs["result"]["oilPriceHistory"]> +export type OilPriceHistorySelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + date?: boolean + province?: boolean + fuel?: boolean + price?: boolean + source?: boolean + createdAt?: boolean +}, ExtArgs["result"]["oilPriceHistory"]> export type OilPriceHistorySelectScalar = { id?: boolean @@ -564,6 +575,30 @@ export interface OilPriceHistoryDelegate(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Create many OilPriceHistories and returns the data saved in the database. + * @param {OilPriceHistoryCreateManyAndReturnArgs} args - Arguments to create many OilPriceHistories. + * @example + * // Create many OilPriceHistories + * const oilPriceHistory = await prisma.oilPriceHistory.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many OilPriceHistories and only return the `id` + * const oilPriceHistoryWithIdOnly = await prisma.oilPriceHistory.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + /** * Delete a OilPriceHistory. * @param {OilPriceHistoryDeleteArgs} args - Arguments to delete one OilPriceHistory. @@ -628,6 +663,36 @@ export interface OilPriceHistoryDelegate(args: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Update zero or more OilPriceHistories and returns the data updated in the database. + * @param {OilPriceHistoryUpdateManyAndReturnArgs} args - Arguments to update many OilPriceHistories. + * @example + * // Update many OilPriceHistories + * const oilPriceHistory = await prisma.oilPriceHistory.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more OilPriceHistories and only return the `id` + * const oilPriceHistoryWithIdOnly = await prisma.oilPriceHistory.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + /** * Create or update one OilPriceHistory. * @param {OilPriceHistoryUpsertArgs} args - Arguments to update or create a OilPriceHistory. @@ -1036,6 +1101,25 @@ export type OilPriceHistoryCreateManyArgs = { + /** + * Select specific fields to fetch from the OilPriceHistory + */ + select?: Prisma.OilPriceHistorySelectCreateManyAndReturn | null + /** + * Omit specific fields from the OilPriceHistory + */ + omit?: Prisma.OilPriceHistoryOmit | null + /** + * The data used to create many OilPriceHistories. + */ + data: Prisma.OilPriceHistoryCreateManyInput | Prisma.OilPriceHistoryCreateManyInput[] + skipDuplicates?: boolean +} + /** * OilPriceHistory update */ @@ -1076,6 +1160,32 @@ export type OilPriceHistoryUpdateManyArgs = { + /** + * Select specific fields to fetch from the OilPriceHistory + */ + select?: Prisma.OilPriceHistorySelectUpdateManyAndReturn | null + /** + * Omit specific fields from the OilPriceHistory + */ + omit?: Prisma.OilPriceHistoryOmit | null + /** + * The data used to update OilPriceHistories. + */ + data: Prisma.XOR + /** + * Filter which OilPriceHistories to update + */ + where?: Prisma.OilPriceHistoryWhereInput + /** + * Limit how many OilPriceHistories to update. + */ + limit?: number +} + /** * OilPriceHistory upsert */ diff --git a/generated/prisma/models/Project.ts b/generated/prisma/models/Project.ts new file mode 100644 index 0000000..f8d51b2 --- /dev/null +++ b/generated/prisma/models/Project.ts @@ -0,0 +1,2129 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `Project` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums.ts" +import type * as Prisma from "../internal/prismaNamespace.ts" + +/** + * Model Project + * + */ +export type ProjectModel = runtime.Types.Result.DefaultSelection + +export type AggregateProject = { + _count: ProjectCountAggregateOutputType | null + _avg: ProjectAvgAggregateOutputType | null + _sum: ProjectSumAggregateOutputType | null + _min: ProjectMinAggregateOutputType | null + _max: ProjectMaxAggregateOutputType | null +} + +export type ProjectAvgAggregateOutputType = { + id: number | null + ownerId: number | null +} + +export type ProjectSumAggregateOutputType = { + id: number | null + ownerId: number | null +} + +export type ProjectMinAggregateOutputType = { + id: number | null + uuid: string | null + code: string | null + name: string | null + ownerId: number | null + isPersonal: boolean | null + archived: boolean | null + icon: string | null + autoArchive: string | null + createdAt: Date | null + updatedAt: Date | null +} + +export type ProjectMaxAggregateOutputType = { + id: number | null + uuid: string | null + code: string | null + name: string | null + ownerId: number | null + isPersonal: boolean | null + archived: boolean | null + icon: string | null + autoArchive: string | null + createdAt: Date | null + updatedAt: Date | null +} + +export type ProjectCountAggregateOutputType = { + id: number + uuid: number + code: number + name: number + ownerId: number + isPersonal: number + archived: number + notes: number + taskKinds: number + icon: number + autoArchive: number + createdAt: number + updatedAt: number + _all: number +} + + +export type ProjectAvgAggregateInputType = { + id?: true + ownerId?: true +} + +export type ProjectSumAggregateInputType = { + id?: true + ownerId?: true +} + +export type ProjectMinAggregateInputType = { + id?: true + uuid?: true + code?: true + name?: true + ownerId?: true + isPersonal?: true + archived?: true + icon?: true + autoArchive?: true + createdAt?: true + updatedAt?: true +} + +export type ProjectMaxAggregateInputType = { + id?: true + uuid?: true + code?: true + name?: true + ownerId?: true + isPersonal?: true + archived?: true + icon?: true + autoArchive?: true + createdAt?: true + updatedAt?: true +} + +export type ProjectCountAggregateInputType = { + id?: true + uuid?: true + code?: true + name?: true + ownerId?: true + isPersonal?: true + archived?: true + notes?: true + taskKinds?: true + icon?: true + autoArchive?: true + createdAt?: true + updatedAt?: true + _all?: true +} + +export type ProjectAggregateArgs = { + /** + * Filter which Project to aggregate. + */ + where?: Prisma.ProjectWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Projects to fetch. + */ + orderBy?: Prisma.ProjectOrderByWithRelationInput | Prisma.ProjectOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.ProjectWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Projects from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Projects. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned Projects + **/ + _count?: true | ProjectCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to average + **/ + _avg?: ProjectAvgAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to sum + **/ + _sum?: ProjectSumAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: ProjectMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: ProjectMaxAggregateInputType +} + +export type GetProjectAggregateType = { + [P in keyof T & keyof AggregateProject]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type ProjectGroupByArgs = { + where?: Prisma.ProjectWhereInput + orderBy?: Prisma.ProjectOrderByWithAggregationInput | Prisma.ProjectOrderByWithAggregationInput[] + by: Prisma.ProjectScalarFieldEnum[] | Prisma.ProjectScalarFieldEnum + having?: Prisma.ProjectScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: ProjectCountAggregateInputType | true + _avg?: ProjectAvgAggregateInputType + _sum?: ProjectSumAggregateInputType + _min?: ProjectMinAggregateInputType + _max?: ProjectMaxAggregateInputType +} + +export type ProjectGroupByOutputType = { + id: number + uuid: string + code: string + name: string + ownerId: number + isPersonal: boolean + archived: boolean + notes: runtime.JsonValue + taskKinds: runtime.JsonValue | null + icon: string | null + autoArchive: string + createdAt: Date + updatedAt: Date + _count: ProjectCountAggregateOutputType | null + _avg: ProjectAvgAggregateOutputType | null + _sum: ProjectSumAggregateOutputType | null + _min: ProjectMinAggregateOutputType | null + _max: ProjectMaxAggregateOutputType | null +} + +export type GetProjectGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof ProjectGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type ProjectWhereInput = { + AND?: Prisma.ProjectWhereInput | Prisma.ProjectWhereInput[] + OR?: Prisma.ProjectWhereInput[] + NOT?: Prisma.ProjectWhereInput | Prisma.ProjectWhereInput[] + id?: Prisma.IntFilter<"Project"> | number + uuid?: Prisma.StringFilter<"Project"> | string + code?: Prisma.StringFilter<"Project"> | string + name?: Prisma.StringFilter<"Project"> | string + ownerId?: Prisma.IntFilter<"Project"> | number + isPersonal?: Prisma.BoolFilter<"Project"> | boolean + archived?: Prisma.BoolFilter<"Project"> | boolean + notes?: Prisma.JsonFilter<"Project"> + taskKinds?: Prisma.JsonNullableFilter<"Project"> + icon?: Prisma.StringNullableFilter<"Project"> | string | null + autoArchive?: Prisma.StringFilter<"Project"> | string + createdAt?: Prisma.DateTimeFilter<"Project"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"Project"> | Date | string + owner?: Prisma.XOR + members?: Prisma.ProjectMemberListRelationFilter + columns?: Prisma.BoardColumnListRelationFilter + tasks?: Prisma.TaskListRelationFilter +} + +export type ProjectOrderByWithRelationInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + code?: Prisma.SortOrder + name?: Prisma.SortOrder + ownerId?: Prisma.SortOrder + isPersonal?: Prisma.SortOrder + archived?: Prisma.SortOrder + notes?: Prisma.SortOrder + taskKinds?: Prisma.SortOrderInput | Prisma.SortOrder + icon?: Prisma.SortOrderInput | Prisma.SortOrder + autoArchive?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + owner?: Prisma.UserOrderByWithRelationInput + members?: Prisma.ProjectMemberOrderByRelationAggregateInput + columns?: Prisma.BoardColumnOrderByRelationAggregateInput + tasks?: Prisma.TaskOrderByRelationAggregateInput +} + +export type ProjectWhereUniqueInput = Prisma.AtLeast<{ + id?: number + uuid?: string + code?: string + AND?: Prisma.ProjectWhereInput | Prisma.ProjectWhereInput[] + OR?: Prisma.ProjectWhereInput[] + NOT?: Prisma.ProjectWhereInput | Prisma.ProjectWhereInput[] + name?: Prisma.StringFilter<"Project"> | string + ownerId?: Prisma.IntFilter<"Project"> | number + isPersonal?: Prisma.BoolFilter<"Project"> | boolean + archived?: Prisma.BoolFilter<"Project"> | boolean + notes?: Prisma.JsonFilter<"Project"> + taskKinds?: Prisma.JsonNullableFilter<"Project"> + icon?: Prisma.StringNullableFilter<"Project"> | string | null + autoArchive?: Prisma.StringFilter<"Project"> | string + createdAt?: Prisma.DateTimeFilter<"Project"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"Project"> | Date | string + owner?: Prisma.XOR + members?: Prisma.ProjectMemberListRelationFilter + columns?: Prisma.BoardColumnListRelationFilter + tasks?: Prisma.TaskListRelationFilter +}, "id" | "uuid" | "code"> + +export type ProjectOrderByWithAggregationInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + code?: Prisma.SortOrder + name?: Prisma.SortOrder + ownerId?: Prisma.SortOrder + isPersonal?: Prisma.SortOrder + archived?: Prisma.SortOrder + notes?: Prisma.SortOrder + taskKinds?: Prisma.SortOrderInput | Prisma.SortOrder + icon?: Prisma.SortOrderInput | Prisma.SortOrder + autoArchive?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + _count?: Prisma.ProjectCountOrderByAggregateInput + _avg?: Prisma.ProjectAvgOrderByAggregateInput + _max?: Prisma.ProjectMaxOrderByAggregateInput + _min?: Prisma.ProjectMinOrderByAggregateInput + _sum?: Prisma.ProjectSumOrderByAggregateInput +} + +export type ProjectScalarWhereWithAggregatesInput = { + AND?: Prisma.ProjectScalarWhereWithAggregatesInput | Prisma.ProjectScalarWhereWithAggregatesInput[] + OR?: Prisma.ProjectScalarWhereWithAggregatesInput[] + NOT?: Prisma.ProjectScalarWhereWithAggregatesInput | Prisma.ProjectScalarWhereWithAggregatesInput[] + id?: Prisma.IntWithAggregatesFilter<"Project"> | number + uuid?: Prisma.StringWithAggregatesFilter<"Project"> | string + code?: Prisma.StringWithAggregatesFilter<"Project"> | string + name?: Prisma.StringWithAggregatesFilter<"Project"> | string + ownerId?: Prisma.IntWithAggregatesFilter<"Project"> | number + isPersonal?: Prisma.BoolWithAggregatesFilter<"Project"> | boolean + archived?: Prisma.BoolWithAggregatesFilter<"Project"> | boolean + notes?: Prisma.JsonWithAggregatesFilter<"Project"> + taskKinds?: Prisma.JsonNullableWithAggregatesFilter<"Project"> + icon?: Prisma.StringNullableWithAggregatesFilter<"Project"> | string | null + autoArchive?: Prisma.StringWithAggregatesFilter<"Project"> | string + createdAt?: Prisma.DateTimeWithAggregatesFilter<"Project"> | Date | string + updatedAt?: Prisma.DateTimeWithAggregatesFilter<"Project"> | Date | string +} + +export type ProjectCreateInput = { + uuid: string + code: string + name: string + isPersonal?: boolean + archived?: boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: string | null + autoArchive?: string + createdAt?: Date | string + updatedAt?: Date | string + owner: Prisma.UserCreateNestedOneWithoutOwnedProjectsInput + members?: Prisma.ProjectMemberCreateNestedManyWithoutProjectInput + columns?: Prisma.BoardColumnCreateNestedManyWithoutProjectInput + tasks?: Prisma.TaskCreateNestedManyWithoutProjectInput +} + +export type ProjectUncheckedCreateInput = { + id?: number + uuid: string + code: string + name: string + ownerId: number + isPersonal?: boolean + archived?: boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: string | null + autoArchive?: string + createdAt?: Date | string + updatedAt?: Date | string + members?: Prisma.ProjectMemberUncheckedCreateNestedManyWithoutProjectInput + columns?: Prisma.BoardColumnUncheckedCreateNestedManyWithoutProjectInput + tasks?: Prisma.TaskUncheckedCreateNestedManyWithoutProjectInput +} + +export type ProjectUpdateInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + code?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + isPersonal?: Prisma.BoolFieldUpdateOperationsInput | boolean + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + autoArchive?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + owner?: Prisma.UserUpdateOneRequiredWithoutOwnedProjectsNestedInput + members?: Prisma.ProjectMemberUpdateManyWithoutProjectNestedInput + columns?: Prisma.BoardColumnUpdateManyWithoutProjectNestedInput + tasks?: Prisma.TaskUpdateManyWithoutProjectNestedInput +} + +export type ProjectUncheckedUpdateInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + code?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + ownerId?: Prisma.IntFieldUpdateOperationsInput | number + isPersonal?: Prisma.BoolFieldUpdateOperationsInput | boolean + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + autoArchive?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + members?: Prisma.ProjectMemberUncheckedUpdateManyWithoutProjectNestedInput + columns?: Prisma.BoardColumnUncheckedUpdateManyWithoutProjectNestedInput + tasks?: Prisma.TaskUncheckedUpdateManyWithoutProjectNestedInput +} + +export type ProjectCreateManyInput = { + id?: number + uuid: string + code: string + name: string + ownerId: number + isPersonal?: boolean + archived?: boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: string | null + autoArchive?: string + createdAt?: Date | string + updatedAt?: Date | string +} + +export type ProjectUpdateManyMutationInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + code?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + isPersonal?: Prisma.BoolFieldUpdateOperationsInput | boolean + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + autoArchive?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type ProjectUncheckedUpdateManyInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + code?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + ownerId?: Prisma.IntFieldUpdateOperationsInput | number + isPersonal?: Prisma.BoolFieldUpdateOperationsInput | boolean + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + autoArchive?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type ProjectListRelationFilter = { + every?: Prisma.ProjectWhereInput + some?: Prisma.ProjectWhereInput + none?: Prisma.ProjectWhereInput +} + +export type ProjectOrderByRelationAggregateInput = { + _count?: Prisma.SortOrder +} + +export type ProjectCountOrderByAggregateInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + code?: Prisma.SortOrder + name?: Prisma.SortOrder + ownerId?: Prisma.SortOrder + isPersonal?: Prisma.SortOrder + archived?: Prisma.SortOrder + notes?: Prisma.SortOrder + taskKinds?: Prisma.SortOrder + icon?: Prisma.SortOrder + autoArchive?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type ProjectAvgOrderByAggregateInput = { + id?: Prisma.SortOrder + ownerId?: Prisma.SortOrder +} + +export type ProjectMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + code?: Prisma.SortOrder + name?: Prisma.SortOrder + ownerId?: Prisma.SortOrder + isPersonal?: Prisma.SortOrder + archived?: Prisma.SortOrder + icon?: Prisma.SortOrder + autoArchive?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type ProjectMinOrderByAggregateInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + code?: Prisma.SortOrder + name?: Prisma.SortOrder + ownerId?: Prisma.SortOrder + isPersonal?: Prisma.SortOrder + archived?: Prisma.SortOrder + icon?: Prisma.SortOrder + autoArchive?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type ProjectSumOrderByAggregateInput = { + id?: Prisma.SortOrder + ownerId?: Prisma.SortOrder +} + +export type ProjectScalarRelationFilter = { + is?: Prisma.ProjectWhereInput + isNot?: Prisma.ProjectWhereInput +} + +export type ProjectCreateNestedManyWithoutOwnerInput = { + create?: Prisma.XOR | Prisma.ProjectCreateWithoutOwnerInput[] | Prisma.ProjectUncheckedCreateWithoutOwnerInput[] + connectOrCreate?: Prisma.ProjectCreateOrConnectWithoutOwnerInput | Prisma.ProjectCreateOrConnectWithoutOwnerInput[] + createMany?: Prisma.ProjectCreateManyOwnerInputEnvelope + connect?: Prisma.ProjectWhereUniqueInput | Prisma.ProjectWhereUniqueInput[] +} + +export type ProjectUncheckedCreateNestedManyWithoutOwnerInput = { + create?: Prisma.XOR | Prisma.ProjectCreateWithoutOwnerInput[] | Prisma.ProjectUncheckedCreateWithoutOwnerInput[] + connectOrCreate?: Prisma.ProjectCreateOrConnectWithoutOwnerInput | Prisma.ProjectCreateOrConnectWithoutOwnerInput[] + createMany?: Prisma.ProjectCreateManyOwnerInputEnvelope + connect?: Prisma.ProjectWhereUniqueInput | Prisma.ProjectWhereUniqueInput[] +} + +export type ProjectUpdateManyWithoutOwnerNestedInput = { + create?: Prisma.XOR | Prisma.ProjectCreateWithoutOwnerInput[] | Prisma.ProjectUncheckedCreateWithoutOwnerInput[] + connectOrCreate?: Prisma.ProjectCreateOrConnectWithoutOwnerInput | Prisma.ProjectCreateOrConnectWithoutOwnerInput[] + upsert?: Prisma.ProjectUpsertWithWhereUniqueWithoutOwnerInput | Prisma.ProjectUpsertWithWhereUniqueWithoutOwnerInput[] + createMany?: Prisma.ProjectCreateManyOwnerInputEnvelope + set?: Prisma.ProjectWhereUniqueInput | Prisma.ProjectWhereUniqueInput[] + disconnect?: Prisma.ProjectWhereUniqueInput | Prisma.ProjectWhereUniqueInput[] + delete?: Prisma.ProjectWhereUniqueInput | Prisma.ProjectWhereUniqueInput[] + connect?: Prisma.ProjectWhereUniqueInput | Prisma.ProjectWhereUniqueInput[] + update?: Prisma.ProjectUpdateWithWhereUniqueWithoutOwnerInput | Prisma.ProjectUpdateWithWhereUniqueWithoutOwnerInput[] + updateMany?: Prisma.ProjectUpdateManyWithWhereWithoutOwnerInput | Prisma.ProjectUpdateManyWithWhereWithoutOwnerInput[] + deleteMany?: Prisma.ProjectScalarWhereInput | Prisma.ProjectScalarWhereInput[] +} + +export type ProjectUncheckedUpdateManyWithoutOwnerNestedInput = { + create?: Prisma.XOR | Prisma.ProjectCreateWithoutOwnerInput[] | Prisma.ProjectUncheckedCreateWithoutOwnerInput[] + connectOrCreate?: Prisma.ProjectCreateOrConnectWithoutOwnerInput | Prisma.ProjectCreateOrConnectWithoutOwnerInput[] + upsert?: Prisma.ProjectUpsertWithWhereUniqueWithoutOwnerInput | Prisma.ProjectUpsertWithWhereUniqueWithoutOwnerInput[] + createMany?: Prisma.ProjectCreateManyOwnerInputEnvelope + set?: Prisma.ProjectWhereUniqueInput | Prisma.ProjectWhereUniqueInput[] + disconnect?: Prisma.ProjectWhereUniqueInput | Prisma.ProjectWhereUniqueInput[] + delete?: Prisma.ProjectWhereUniqueInput | Prisma.ProjectWhereUniqueInput[] + connect?: Prisma.ProjectWhereUniqueInput | Prisma.ProjectWhereUniqueInput[] + update?: Prisma.ProjectUpdateWithWhereUniqueWithoutOwnerInput | Prisma.ProjectUpdateWithWhereUniqueWithoutOwnerInput[] + updateMany?: Prisma.ProjectUpdateManyWithWhereWithoutOwnerInput | Prisma.ProjectUpdateManyWithWhereWithoutOwnerInput[] + deleteMany?: Prisma.ProjectScalarWhereInput | Prisma.ProjectScalarWhereInput[] +} + +export type ProjectCreateNestedOneWithoutMembersInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.ProjectCreateOrConnectWithoutMembersInput + connect?: Prisma.ProjectWhereUniqueInput +} + +export type ProjectUpdateOneRequiredWithoutMembersNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.ProjectCreateOrConnectWithoutMembersInput + upsert?: Prisma.ProjectUpsertWithoutMembersInput + connect?: Prisma.ProjectWhereUniqueInput + update?: Prisma.XOR, Prisma.ProjectUncheckedUpdateWithoutMembersInput> +} + +export type ProjectCreateNestedOneWithoutColumnsInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.ProjectCreateOrConnectWithoutColumnsInput + connect?: Prisma.ProjectWhereUniqueInput +} + +export type ProjectUpdateOneRequiredWithoutColumnsNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.ProjectCreateOrConnectWithoutColumnsInput + upsert?: Prisma.ProjectUpsertWithoutColumnsInput + connect?: Prisma.ProjectWhereUniqueInput + update?: Prisma.XOR, Prisma.ProjectUncheckedUpdateWithoutColumnsInput> +} + +export type ProjectCreateNestedOneWithoutTasksInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.ProjectCreateOrConnectWithoutTasksInput + connect?: Prisma.ProjectWhereUniqueInput +} + +export type ProjectUpdateOneRequiredWithoutTasksNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.ProjectCreateOrConnectWithoutTasksInput + upsert?: Prisma.ProjectUpsertWithoutTasksInput + connect?: Prisma.ProjectWhereUniqueInput + update?: Prisma.XOR, Prisma.ProjectUncheckedUpdateWithoutTasksInput> +} + +export type ProjectCreateWithoutOwnerInput = { + uuid: string + code: string + name: string + isPersonal?: boolean + archived?: boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: string | null + autoArchive?: string + createdAt?: Date | string + updatedAt?: Date | string + members?: Prisma.ProjectMemberCreateNestedManyWithoutProjectInput + columns?: Prisma.BoardColumnCreateNestedManyWithoutProjectInput + tasks?: Prisma.TaskCreateNestedManyWithoutProjectInput +} + +export type ProjectUncheckedCreateWithoutOwnerInput = { + id?: number + uuid: string + code: string + name: string + isPersonal?: boolean + archived?: boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: string | null + autoArchive?: string + createdAt?: Date | string + updatedAt?: Date | string + members?: Prisma.ProjectMemberUncheckedCreateNestedManyWithoutProjectInput + columns?: Prisma.BoardColumnUncheckedCreateNestedManyWithoutProjectInput + tasks?: Prisma.TaskUncheckedCreateNestedManyWithoutProjectInput +} + +export type ProjectCreateOrConnectWithoutOwnerInput = { + where: Prisma.ProjectWhereUniqueInput + create: Prisma.XOR +} + +export type ProjectCreateManyOwnerInputEnvelope = { + data: Prisma.ProjectCreateManyOwnerInput | Prisma.ProjectCreateManyOwnerInput[] + skipDuplicates?: boolean +} + +export type ProjectUpsertWithWhereUniqueWithoutOwnerInput = { + where: Prisma.ProjectWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type ProjectUpdateWithWhereUniqueWithoutOwnerInput = { + where: Prisma.ProjectWhereUniqueInput + data: Prisma.XOR +} + +export type ProjectUpdateManyWithWhereWithoutOwnerInput = { + where: Prisma.ProjectScalarWhereInput + data: Prisma.XOR +} + +export type ProjectScalarWhereInput = { + AND?: Prisma.ProjectScalarWhereInput | Prisma.ProjectScalarWhereInput[] + OR?: Prisma.ProjectScalarWhereInput[] + NOT?: Prisma.ProjectScalarWhereInput | Prisma.ProjectScalarWhereInput[] + id?: Prisma.IntFilter<"Project"> | number + uuid?: Prisma.StringFilter<"Project"> | string + code?: Prisma.StringFilter<"Project"> | string + name?: Prisma.StringFilter<"Project"> | string + ownerId?: Prisma.IntFilter<"Project"> | number + isPersonal?: Prisma.BoolFilter<"Project"> | boolean + archived?: Prisma.BoolFilter<"Project"> | boolean + notes?: Prisma.JsonFilter<"Project"> + taskKinds?: Prisma.JsonNullableFilter<"Project"> + icon?: Prisma.StringNullableFilter<"Project"> | string | null + autoArchive?: Prisma.StringFilter<"Project"> | string + createdAt?: Prisma.DateTimeFilter<"Project"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"Project"> | Date | string +} + +export type ProjectCreateWithoutMembersInput = { + uuid: string + code: string + name: string + isPersonal?: boolean + archived?: boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: string | null + autoArchive?: string + createdAt?: Date | string + updatedAt?: Date | string + owner: Prisma.UserCreateNestedOneWithoutOwnedProjectsInput + columns?: Prisma.BoardColumnCreateNestedManyWithoutProjectInput + tasks?: Prisma.TaskCreateNestedManyWithoutProjectInput +} + +export type ProjectUncheckedCreateWithoutMembersInput = { + id?: number + uuid: string + code: string + name: string + ownerId: number + isPersonal?: boolean + archived?: boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: string | null + autoArchive?: string + createdAt?: Date | string + updatedAt?: Date | string + columns?: Prisma.BoardColumnUncheckedCreateNestedManyWithoutProjectInput + tasks?: Prisma.TaskUncheckedCreateNestedManyWithoutProjectInput +} + +export type ProjectCreateOrConnectWithoutMembersInput = { + where: Prisma.ProjectWhereUniqueInput + create: Prisma.XOR +} + +export type ProjectUpsertWithoutMembersInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.ProjectWhereInput +} + +export type ProjectUpdateToOneWithWhereWithoutMembersInput = { + where?: Prisma.ProjectWhereInput + data: Prisma.XOR +} + +export type ProjectUpdateWithoutMembersInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + code?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + isPersonal?: Prisma.BoolFieldUpdateOperationsInput | boolean + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + autoArchive?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + owner?: Prisma.UserUpdateOneRequiredWithoutOwnedProjectsNestedInput + columns?: Prisma.BoardColumnUpdateManyWithoutProjectNestedInput + tasks?: Prisma.TaskUpdateManyWithoutProjectNestedInput +} + +export type ProjectUncheckedUpdateWithoutMembersInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + code?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + ownerId?: Prisma.IntFieldUpdateOperationsInput | number + isPersonal?: Prisma.BoolFieldUpdateOperationsInput | boolean + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + autoArchive?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + columns?: Prisma.BoardColumnUncheckedUpdateManyWithoutProjectNestedInput + tasks?: Prisma.TaskUncheckedUpdateManyWithoutProjectNestedInput +} + +export type ProjectCreateWithoutColumnsInput = { + uuid: string + code: string + name: string + isPersonal?: boolean + archived?: boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: string | null + autoArchive?: string + createdAt?: Date | string + updatedAt?: Date | string + owner: Prisma.UserCreateNestedOneWithoutOwnedProjectsInput + members?: Prisma.ProjectMemberCreateNestedManyWithoutProjectInput + tasks?: Prisma.TaskCreateNestedManyWithoutProjectInput +} + +export type ProjectUncheckedCreateWithoutColumnsInput = { + id?: number + uuid: string + code: string + name: string + ownerId: number + isPersonal?: boolean + archived?: boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: string | null + autoArchive?: string + createdAt?: Date | string + updatedAt?: Date | string + members?: Prisma.ProjectMemberUncheckedCreateNestedManyWithoutProjectInput + tasks?: Prisma.TaskUncheckedCreateNestedManyWithoutProjectInput +} + +export type ProjectCreateOrConnectWithoutColumnsInput = { + where: Prisma.ProjectWhereUniqueInput + create: Prisma.XOR +} + +export type ProjectUpsertWithoutColumnsInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.ProjectWhereInput +} + +export type ProjectUpdateToOneWithWhereWithoutColumnsInput = { + where?: Prisma.ProjectWhereInput + data: Prisma.XOR +} + +export type ProjectUpdateWithoutColumnsInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + code?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + isPersonal?: Prisma.BoolFieldUpdateOperationsInput | boolean + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + autoArchive?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + owner?: Prisma.UserUpdateOneRequiredWithoutOwnedProjectsNestedInput + members?: Prisma.ProjectMemberUpdateManyWithoutProjectNestedInput + tasks?: Prisma.TaskUpdateManyWithoutProjectNestedInput +} + +export type ProjectUncheckedUpdateWithoutColumnsInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + code?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + ownerId?: Prisma.IntFieldUpdateOperationsInput | number + isPersonal?: Prisma.BoolFieldUpdateOperationsInput | boolean + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + autoArchive?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + members?: Prisma.ProjectMemberUncheckedUpdateManyWithoutProjectNestedInput + tasks?: Prisma.TaskUncheckedUpdateManyWithoutProjectNestedInput +} + +export type ProjectCreateWithoutTasksInput = { + uuid: string + code: string + name: string + isPersonal?: boolean + archived?: boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: string | null + autoArchive?: string + createdAt?: Date | string + updatedAt?: Date | string + owner: Prisma.UserCreateNestedOneWithoutOwnedProjectsInput + members?: Prisma.ProjectMemberCreateNestedManyWithoutProjectInput + columns?: Prisma.BoardColumnCreateNestedManyWithoutProjectInput +} + +export type ProjectUncheckedCreateWithoutTasksInput = { + id?: number + uuid: string + code: string + name: string + ownerId: number + isPersonal?: boolean + archived?: boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: string | null + autoArchive?: string + createdAt?: Date | string + updatedAt?: Date | string + members?: Prisma.ProjectMemberUncheckedCreateNestedManyWithoutProjectInput + columns?: Prisma.BoardColumnUncheckedCreateNestedManyWithoutProjectInput +} + +export type ProjectCreateOrConnectWithoutTasksInput = { + where: Prisma.ProjectWhereUniqueInput + create: Prisma.XOR +} + +export type ProjectUpsertWithoutTasksInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.ProjectWhereInput +} + +export type ProjectUpdateToOneWithWhereWithoutTasksInput = { + where?: Prisma.ProjectWhereInput + data: Prisma.XOR +} + +export type ProjectUpdateWithoutTasksInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + code?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + isPersonal?: Prisma.BoolFieldUpdateOperationsInput | boolean + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + autoArchive?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + owner?: Prisma.UserUpdateOneRequiredWithoutOwnedProjectsNestedInput + members?: Prisma.ProjectMemberUpdateManyWithoutProjectNestedInput + columns?: Prisma.BoardColumnUpdateManyWithoutProjectNestedInput +} + +export type ProjectUncheckedUpdateWithoutTasksInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + code?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + ownerId?: Prisma.IntFieldUpdateOperationsInput | number + isPersonal?: Prisma.BoolFieldUpdateOperationsInput | boolean + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + autoArchive?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + members?: Prisma.ProjectMemberUncheckedUpdateManyWithoutProjectNestedInput + columns?: Prisma.BoardColumnUncheckedUpdateManyWithoutProjectNestedInput +} + +export type ProjectCreateManyOwnerInput = { + id?: number + uuid: string + code: string + name: string + isPersonal?: boolean + archived?: boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: string | null + autoArchive?: string + createdAt?: Date | string + updatedAt?: Date | string +} + +export type ProjectUpdateWithoutOwnerInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + code?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + isPersonal?: Prisma.BoolFieldUpdateOperationsInput | boolean + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + autoArchive?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + members?: Prisma.ProjectMemberUpdateManyWithoutProjectNestedInput + columns?: Prisma.BoardColumnUpdateManyWithoutProjectNestedInput + tasks?: Prisma.TaskUpdateManyWithoutProjectNestedInput +} + +export type ProjectUncheckedUpdateWithoutOwnerInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + code?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + isPersonal?: Prisma.BoolFieldUpdateOperationsInput | boolean + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + autoArchive?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + members?: Prisma.ProjectMemberUncheckedUpdateManyWithoutProjectNestedInput + columns?: Prisma.BoardColumnUncheckedUpdateManyWithoutProjectNestedInput + tasks?: Prisma.TaskUncheckedUpdateManyWithoutProjectNestedInput +} + +export type ProjectUncheckedUpdateManyWithoutOwnerInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + code?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + isPersonal?: Prisma.BoolFieldUpdateOperationsInput | boolean + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + notes?: Prisma.JsonNullValueInput | runtime.InputJsonValue + taskKinds?: Prisma.NullableJsonNullValueInput | runtime.InputJsonValue + icon?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + autoArchive?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + + +/** + * Count Type ProjectCountOutputType + */ + +export type ProjectCountOutputType = { + members: number + columns: number + tasks: number +} + +export type ProjectCountOutputTypeSelect = { + members?: boolean | ProjectCountOutputTypeCountMembersArgs + columns?: boolean | ProjectCountOutputTypeCountColumnsArgs + tasks?: boolean | ProjectCountOutputTypeCountTasksArgs +} + +/** + * ProjectCountOutputType without action + */ +export type ProjectCountOutputTypeDefaultArgs = { + /** + * Select specific fields to fetch from the ProjectCountOutputType + */ + select?: Prisma.ProjectCountOutputTypeSelect | null +} + +/** + * ProjectCountOutputType without action + */ +export type ProjectCountOutputTypeCountMembersArgs = { + where?: Prisma.ProjectMemberWhereInput +} + +/** + * ProjectCountOutputType without action + */ +export type ProjectCountOutputTypeCountColumnsArgs = { + where?: Prisma.BoardColumnWhereInput +} + +/** + * ProjectCountOutputType without action + */ +export type ProjectCountOutputTypeCountTasksArgs = { + where?: Prisma.TaskWhereInput +} + + +export type ProjectSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + code?: boolean + name?: boolean + ownerId?: boolean + isPersonal?: boolean + archived?: boolean + notes?: boolean + taskKinds?: boolean + icon?: boolean + autoArchive?: boolean + createdAt?: boolean + updatedAt?: boolean + owner?: boolean | Prisma.UserDefaultArgs + members?: boolean | Prisma.Project$membersArgs + columns?: boolean | Prisma.Project$columnsArgs + tasks?: boolean | Prisma.Project$tasksArgs + _count?: boolean | Prisma.ProjectCountOutputTypeDefaultArgs +}, ExtArgs["result"]["project"]> + +export type ProjectSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + code?: boolean + name?: boolean + ownerId?: boolean + isPersonal?: boolean + archived?: boolean + notes?: boolean + taskKinds?: boolean + icon?: boolean + autoArchive?: boolean + createdAt?: boolean + updatedAt?: boolean + owner?: boolean | Prisma.UserDefaultArgs +}, ExtArgs["result"]["project"]> + +export type ProjectSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + code?: boolean + name?: boolean + ownerId?: boolean + isPersonal?: boolean + archived?: boolean + notes?: boolean + taskKinds?: boolean + icon?: boolean + autoArchive?: boolean + createdAt?: boolean + updatedAt?: boolean + owner?: boolean | Prisma.UserDefaultArgs +}, ExtArgs["result"]["project"]> + +export type ProjectSelectScalar = { + id?: boolean + uuid?: boolean + code?: boolean + name?: boolean + ownerId?: boolean + isPersonal?: boolean + archived?: boolean + notes?: boolean + taskKinds?: boolean + icon?: boolean + autoArchive?: boolean + createdAt?: boolean + updatedAt?: boolean +} + +export type ProjectOmit = runtime.Types.Extensions.GetOmit<"id" | "uuid" | "code" | "name" | "ownerId" | "isPersonal" | "archived" | "notes" | "taskKinds" | "icon" | "autoArchive" | "createdAt" | "updatedAt", ExtArgs["result"]["project"]> +export type ProjectInclude = { + owner?: boolean | Prisma.UserDefaultArgs + members?: boolean | Prisma.Project$membersArgs + columns?: boolean | Prisma.Project$columnsArgs + tasks?: boolean | Prisma.Project$tasksArgs + _count?: boolean | Prisma.ProjectCountOutputTypeDefaultArgs +} +export type ProjectIncludeCreateManyAndReturn = { + owner?: boolean | Prisma.UserDefaultArgs +} +export type ProjectIncludeUpdateManyAndReturn = { + owner?: boolean | Prisma.UserDefaultArgs +} + +export type $ProjectPayload = { + name: "Project" + objects: { + owner: Prisma.$UserPayload + members: Prisma.$ProjectMemberPayload[] + columns: Prisma.$BoardColumnPayload[] + tasks: Prisma.$TaskPayload[] + } + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: number + uuid: string + code: string + name: string + ownerId: number + isPersonal: boolean + archived: boolean + notes: runtime.JsonValue + taskKinds: runtime.JsonValue | null + icon: string | null + autoArchive: string + createdAt: Date + updatedAt: Date + }, ExtArgs["result"]["project"]> + composites: {} +} + +export type ProjectGetPayload = runtime.Types.Result.GetResult + +export type ProjectCountArgs = + Omit & { + select?: ProjectCountAggregateInputType | true + } + +export interface ProjectDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['Project'], meta: { name: 'Project' } } + /** + * Find zero or one Project that matches the filter. + * @param {ProjectFindUniqueArgs} args - Arguments to find a Project + * @example + * // Get one Project + * const project = await prisma.project.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__ProjectClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one Project that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {ProjectFindUniqueOrThrowArgs} args - Arguments to find a Project + * @example + * // Get one Project + * const project = await prisma.project.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__ProjectClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first Project that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ProjectFindFirstArgs} args - Arguments to find a Project + * @example + * // Get one Project + * const project = await prisma.project.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__ProjectClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first Project that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ProjectFindFirstOrThrowArgs} args - Arguments to find a Project + * @example + * // Get one Project + * const project = await prisma.project.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__ProjectClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more Projects that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ProjectFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all Projects + * const projects = await prisma.project.findMany() + * + * // Get first 10 Projects + * const projects = await prisma.project.findMany({ take: 10 }) + * + * // Only select the `id` + * const projectWithIdOnly = await prisma.project.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a Project. + * @param {ProjectCreateArgs} args - Arguments to create a Project. + * @example + * // Create one Project + * const Project = await prisma.project.create({ + * data: { + * // ... data to create a Project + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__ProjectClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many Projects. + * @param {ProjectCreateManyArgs} args - Arguments to create many Projects. + * @example + * // Create many Projects + * const project = await prisma.project.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many Projects and returns the data saved in the database. + * @param {ProjectCreateManyAndReturnArgs} args - Arguments to create many Projects. + * @example + * // Create many Projects + * const project = await prisma.project.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many Projects and only return the `id` + * const projectWithIdOnly = await prisma.project.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a Project. + * @param {ProjectDeleteArgs} args - Arguments to delete one Project. + * @example + * // Delete one Project + * const Project = await prisma.project.delete({ + * where: { + * // ... filter to delete one Project + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__ProjectClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one Project. + * @param {ProjectUpdateArgs} args - Arguments to update one Project. + * @example + * // Update one Project + * const project = await prisma.project.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__ProjectClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more Projects. + * @param {ProjectDeleteManyArgs} args - Arguments to filter Projects to delete. + * @example + * // Delete a few Projects + * const { count } = await prisma.project.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more Projects. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ProjectUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many Projects + * const project = await prisma.project.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more Projects and returns the data updated in the database. + * @param {ProjectUpdateManyAndReturnArgs} args - Arguments to update many Projects. + * @example + * // Update many Projects + * const project = await prisma.project.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more Projects and only return the `id` + * const projectWithIdOnly = await prisma.project.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one Project. + * @param {ProjectUpsertArgs} args - Arguments to update or create a Project. + * @example + * // Update or create a Project + * const project = await prisma.project.upsert({ + * create: { + * // ... data to create a Project + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the Project we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__ProjectClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of Projects. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ProjectCountArgs} args - Arguments to filter Projects to count. + * @example + * // Count the number of Projects + * const count = await prisma.project.count({ + * where: { + * // ... the filter for the Projects we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a Project. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ProjectAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by Project. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ProjectGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends ProjectGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: ProjectGroupByArgs['orderBy'] } + : { orderBy?: ProjectGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetProjectGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the Project model + */ +readonly fields: ProjectFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for Project. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__ProjectClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + owner = {}>(args?: Prisma.Subset>): Prisma.Prisma__UserClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + members = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + columns = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + tasks = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the Project model + */ +export interface ProjectFieldRefs { + readonly id: Prisma.FieldRef<"Project", 'Int'> + readonly uuid: Prisma.FieldRef<"Project", 'String'> + readonly code: Prisma.FieldRef<"Project", 'String'> + readonly name: Prisma.FieldRef<"Project", 'String'> + readonly ownerId: Prisma.FieldRef<"Project", 'Int'> + readonly isPersonal: Prisma.FieldRef<"Project", 'Boolean'> + readonly archived: Prisma.FieldRef<"Project", 'Boolean'> + readonly notes: Prisma.FieldRef<"Project", 'Json'> + readonly taskKinds: Prisma.FieldRef<"Project", 'Json'> + readonly icon: Prisma.FieldRef<"Project", 'String'> + readonly autoArchive: Prisma.FieldRef<"Project", 'String'> + readonly createdAt: Prisma.FieldRef<"Project", 'DateTime'> + readonly updatedAt: Prisma.FieldRef<"Project", 'DateTime'> +} + + +// Custom InputTypes +/** + * Project findUnique + */ +export type ProjectFindUniqueArgs = { + /** + * Select specific fields to fetch from the Project + */ + select?: Prisma.ProjectSelect | null + /** + * Omit specific fields from the Project + */ + omit?: Prisma.ProjectOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectInclude | null + /** + * Filter, which Project to fetch. + */ + where: Prisma.ProjectWhereUniqueInput +} + +/** + * Project findUniqueOrThrow + */ +export type ProjectFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the Project + */ + select?: Prisma.ProjectSelect | null + /** + * Omit specific fields from the Project + */ + omit?: Prisma.ProjectOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectInclude | null + /** + * Filter, which Project to fetch. + */ + where: Prisma.ProjectWhereUniqueInput +} + +/** + * Project findFirst + */ +export type ProjectFindFirstArgs = { + /** + * Select specific fields to fetch from the Project + */ + select?: Prisma.ProjectSelect | null + /** + * Omit specific fields from the Project + */ + omit?: Prisma.ProjectOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectInclude | null + /** + * Filter, which Project to fetch. + */ + where?: Prisma.ProjectWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Projects to fetch. + */ + orderBy?: Prisma.ProjectOrderByWithRelationInput | Prisma.ProjectOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for Projects. + */ + cursor?: Prisma.ProjectWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Projects from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Projects. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Projects. + */ + distinct?: Prisma.ProjectScalarFieldEnum | Prisma.ProjectScalarFieldEnum[] +} + +/** + * Project findFirstOrThrow + */ +export type ProjectFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the Project + */ + select?: Prisma.ProjectSelect | null + /** + * Omit specific fields from the Project + */ + omit?: Prisma.ProjectOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectInclude | null + /** + * Filter, which Project to fetch. + */ + where?: Prisma.ProjectWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Projects to fetch. + */ + orderBy?: Prisma.ProjectOrderByWithRelationInput | Prisma.ProjectOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for Projects. + */ + cursor?: Prisma.ProjectWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Projects from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Projects. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Projects. + */ + distinct?: Prisma.ProjectScalarFieldEnum | Prisma.ProjectScalarFieldEnum[] +} + +/** + * Project findMany + */ +export type ProjectFindManyArgs = { + /** + * Select specific fields to fetch from the Project + */ + select?: Prisma.ProjectSelect | null + /** + * Omit specific fields from the Project + */ + omit?: Prisma.ProjectOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectInclude | null + /** + * Filter, which Projects to fetch. + */ + where?: Prisma.ProjectWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Projects to fetch. + */ + orderBy?: Prisma.ProjectOrderByWithRelationInput | Prisma.ProjectOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing Projects. + */ + cursor?: Prisma.ProjectWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Projects from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Projects. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Projects. + */ + distinct?: Prisma.ProjectScalarFieldEnum | Prisma.ProjectScalarFieldEnum[] +} + +/** + * Project create + */ +export type ProjectCreateArgs = { + /** + * Select specific fields to fetch from the Project + */ + select?: Prisma.ProjectSelect | null + /** + * Omit specific fields from the Project + */ + omit?: Prisma.ProjectOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectInclude | null + /** + * The data needed to create a Project. + */ + data: Prisma.XOR +} + +/** + * Project createMany + */ +export type ProjectCreateManyArgs = { + /** + * The data used to create many Projects. + */ + data: Prisma.ProjectCreateManyInput | Prisma.ProjectCreateManyInput[] + skipDuplicates?: boolean +} + +/** + * Project createManyAndReturn + */ +export type ProjectCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the Project + */ + select?: Prisma.ProjectSelectCreateManyAndReturn | null + /** + * Omit specific fields from the Project + */ + omit?: Prisma.ProjectOmit | null + /** + * The data used to create many Projects. + */ + data: Prisma.ProjectCreateManyInput | Prisma.ProjectCreateManyInput[] + skipDuplicates?: boolean + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectIncludeCreateManyAndReturn | null +} + +/** + * Project update + */ +export type ProjectUpdateArgs = { + /** + * Select specific fields to fetch from the Project + */ + select?: Prisma.ProjectSelect | null + /** + * Omit specific fields from the Project + */ + omit?: Prisma.ProjectOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectInclude | null + /** + * The data needed to update a Project. + */ + data: Prisma.XOR + /** + * Choose, which Project to update. + */ + where: Prisma.ProjectWhereUniqueInput +} + +/** + * Project updateMany + */ +export type ProjectUpdateManyArgs = { + /** + * The data used to update Projects. + */ + data: Prisma.XOR + /** + * Filter which Projects to update + */ + where?: Prisma.ProjectWhereInput + /** + * Limit how many Projects to update. + */ + limit?: number +} + +/** + * Project updateManyAndReturn + */ +export type ProjectUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the Project + */ + select?: Prisma.ProjectSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the Project + */ + omit?: Prisma.ProjectOmit | null + /** + * The data used to update Projects. + */ + data: Prisma.XOR + /** + * Filter which Projects to update + */ + where?: Prisma.ProjectWhereInput + /** + * Limit how many Projects to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectIncludeUpdateManyAndReturn | null +} + +/** + * Project upsert + */ +export type ProjectUpsertArgs = { + /** + * Select specific fields to fetch from the Project + */ + select?: Prisma.ProjectSelect | null + /** + * Omit specific fields from the Project + */ + omit?: Prisma.ProjectOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectInclude | null + /** + * The filter to search for the Project to update in case it exists. + */ + where: Prisma.ProjectWhereUniqueInput + /** + * In case the Project found by the `where` argument doesn't exist, create a new Project with this data. + */ + create: Prisma.XOR + /** + * In case the Project was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * Project delete + */ +export type ProjectDeleteArgs = { + /** + * Select specific fields to fetch from the Project + */ + select?: Prisma.ProjectSelect | null + /** + * Omit specific fields from the Project + */ + omit?: Prisma.ProjectOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectInclude | null + /** + * Filter which Project to delete. + */ + where: Prisma.ProjectWhereUniqueInput +} + +/** + * Project deleteMany + */ +export type ProjectDeleteManyArgs = { + /** + * Filter which Projects to delete + */ + where?: Prisma.ProjectWhereInput + /** + * Limit how many Projects to delete. + */ + limit?: number +} + +/** + * Project.members + */ +export type Project$membersArgs = { + /** + * Select specific fields to fetch from the ProjectMember + */ + select?: Prisma.ProjectMemberSelect | null + /** + * Omit specific fields from the ProjectMember + */ + omit?: Prisma.ProjectMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectMemberInclude | null + where?: Prisma.ProjectMemberWhereInput + orderBy?: Prisma.ProjectMemberOrderByWithRelationInput | Prisma.ProjectMemberOrderByWithRelationInput[] + cursor?: Prisma.ProjectMemberWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.ProjectMemberScalarFieldEnum | Prisma.ProjectMemberScalarFieldEnum[] +} + +/** + * Project.columns + */ +export type Project$columnsArgs = { + /** + * Select specific fields to fetch from the BoardColumn + */ + select?: Prisma.BoardColumnSelect | null + /** + * Omit specific fields from the BoardColumn + */ + omit?: Prisma.BoardColumnOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.BoardColumnInclude | null + where?: Prisma.BoardColumnWhereInput + orderBy?: Prisma.BoardColumnOrderByWithRelationInput | Prisma.BoardColumnOrderByWithRelationInput[] + cursor?: Prisma.BoardColumnWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.BoardColumnScalarFieldEnum | Prisma.BoardColumnScalarFieldEnum[] +} + +/** + * Project.tasks + */ +export type Project$tasksArgs = { + /** + * Select specific fields to fetch from the Task + */ + select?: Prisma.TaskSelect | null + /** + * Omit specific fields from the Task + */ + omit?: Prisma.TaskOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskInclude | null + where?: Prisma.TaskWhereInput + orderBy?: Prisma.TaskOrderByWithRelationInput | Prisma.TaskOrderByWithRelationInput[] + cursor?: Prisma.TaskWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.TaskScalarFieldEnum | Prisma.TaskScalarFieldEnum[] +} + +/** + * Project without action + */ +export type ProjectDefaultArgs = { + /** + * Select specific fields to fetch from the Project + */ + select?: Prisma.ProjectSelect | null + /** + * Omit specific fields from the Project + */ + omit?: Prisma.ProjectOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectInclude | null +} diff --git a/generated/prisma/models/ProjectMember.ts b/generated/prisma/models/ProjectMember.ts new file mode 100644 index 0000000..8ec5491 --- /dev/null +++ b/generated/prisma/models/ProjectMember.ts @@ -0,0 +1,1602 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `ProjectMember` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums.ts" +import type * as Prisma from "../internal/prismaNamespace.ts" + +/** + * Model ProjectMember + * + */ +export type ProjectMemberModel = runtime.Types.Result.DefaultSelection + +export type AggregateProjectMember = { + _count: ProjectMemberCountAggregateOutputType | null + _avg: ProjectMemberAvgAggregateOutputType | null + _sum: ProjectMemberSumAggregateOutputType | null + _min: ProjectMemberMinAggregateOutputType | null + _max: ProjectMemberMaxAggregateOutputType | null +} + +export type ProjectMemberAvgAggregateOutputType = { + id: number | null + projectId: number | null + userId: number | null +} + +export type ProjectMemberSumAggregateOutputType = { + id: number | null + projectId: number | null + userId: number | null +} + +export type ProjectMemberMinAggregateOutputType = { + id: number | null + uuid: string | null + projectUuid: string | null + projectId: number | null + userId: number | null + permission: string | null + createdAt: Date | null +} + +export type ProjectMemberMaxAggregateOutputType = { + id: number | null + uuid: string | null + projectUuid: string | null + projectId: number | null + userId: number | null + permission: string | null + createdAt: Date | null +} + +export type ProjectMemberCountAggregateOutputType = { + id: number + uuid: number + projectUuid: number + projectId: number + userId: number + permission: number + createdAt: number + _all: number +} + + +export type ProjectMemberAvgAggregateInputType = { + id?: true + projectId?: true + userId?: true +} + +export type ProjectMemberSumAggregateInputType = { + id?: true + projectId?: true + userId?: true +} + +export type ProjectMemberMinAggregateInputType = { + id?: true + uuid?: true + projectUuid?: true + projectId?: true + userId?: true + permission?: true + createdAt?: true +} + +export type ProjectMemberMaxAggregateInputType = { + id?: true + uuid?: true + projectUuid?: true + projectId?: true + userId?: true + permission?: true + createdAt?: true +} + +export type ProjectMemberCountAggregateInputType = { + id?: true + uuid?: true + projectUuid?: true + projectId?: true + userId?: true + permission?: true + createdAt?: true + _all?: true +} + +export type ProjectMemberAggregateArgs = { + /** + * Filter which ProjectMember to aggregate. + */ + where?: Prisma.ProjectMemberWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of ProjectMembers to fetch. + */ + orderBy?: Prisma.ProjectMemberOrderByWithRelationInput | Prisma.ProjectMemberOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.ProjectMemberWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` ProjectMembers from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` ProjectMembers. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned ProjectMembers + **/ + _count?: true | ProjectMemberCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to average + **/ + _avg?: ProjectMemberAvgAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to sum + **/ + _sum?: ProjectMemberSumAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: ProjectMemberMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: ProjectMemberMaxAggregateInputType +} + +export type GetProjectMemberAggregateType = { + [P in keyof T & keyof AggregateProjectMember]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type ProjectMemberGroupByArgs = { + where?: Prisma.ProjectMemberWhereInput + orderBy?: Prisma.ProjectMemberOrderByWithAggregationInput | Prisma.ProjectMemberOrderByWithAggregationInput[] + by: Prisma.ProjectMemberScalarFieldEnum[] | Prisma.ProjectMemberScalarFieldEnum + having?: Prisma.ProjectMemberScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: ProjectMemberCountAggregateInputType | true + _avg?: ProjectMemberAvgAggregateInputType + _sum?: ProjectMemberSumAggregateInputType + _min?: ProjectMemberMinAggregateInputType + _max?: ProjectMemberMaxAggregateInputType +} + +export type ProjectMemberGroupByOutputType = { + id: number + uuid: string + projectUuid: string | null + projectId: number + userId: number + permission: string + createdAt: Date + _count: ProjectMemberCountAggregateOutputType | null + _avg: ProjectMemberAvgAggregateOutputType | null + _sum: ProjectMemberSumAggregateOutputType | null + _min: ProjectMemberMinAggregateOutputType | null + _max: ProjectMemberMaxAggregateOutputType | null +} + +export type GetProjectMemberGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof ProjectMemberGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type ProjectMemberWhereInput = { + AND?: Prisma.ProjectMemberWhereInput | Prisma.ProjectMemberWhereInput[] + OR?: Prisma.ProjectMemberWhereInput[] + NOT?: Prisma.ProjectMemberWhereInput | Prisma.ProjectMemberWhereInput[] + id?: Prisma.IntFilter<"ProjectMember"> | number + uuid?: Prisma.StringFilter<"ProjectMember"> | string + projectUuid?: Prisma.StringNullableFilter<"ProjectMember"> | string | null + projectId?: Prisma.IntFilter<"ProjectMember"> | number + userId?: Prisma.IntFilter<"ProjectMember"> | number + permission?: Prisma.StringFilter<"ProjectMember"> | string + createdAt?: Prisma.DateTimeFilter<"ProjectMember"> | Date | string + project?: Prisma.XOR + user?: Prisma.XOR +} + +export type ProjectMemberOrderByWithRelationInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + projectUuid?: Prisma.SortOrderInput | Prisma.SortOrder + projectId?: Prisma.SortOrder + userId?: Prisma.SortOrder + permission?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + project?: Prisma.ProjectOrderByWithRelationInput + user?: Prisma.UserOrderByWithRelationInput +} + +export type ProjectMemberWhereUniqueInput = Prisma.AtLeast<{ + id?: number + uuid?: string + projectId_userId?: Prisma.ProjectMemberProjectIdUserIdCompoundUniqueInput + AND?: Prisma.ProjectMemberWhereInput | Prisma.ProjectMemberWhereInput[] + OR?: Prisma.ProjectMemberWhereInput[] + NOT?: Prisma.ProjectMemberWhereInput | Prisma.ProjectMemberWhereInput[] + projectUuid?: Prisma.StringNullableFilter<"ProjectMember"> | string | null + projectId?: Prisma.IntFilter<"ProjectMember"> | number + userId?: Prisma.IntFilter<"ProjectMember"> | number + permission?: Prisma.StringFilter<"ProjectMember"> | string + createdAt?: Prisma.DateTimeFilter<"ProjectMember"> | Date | string + project?: Prisma.XOR + user?: Prisma.XOR +}, "id" | "uuid" | "projectId_userId"> + +export type ProjectMemberOrderByWithAggregationInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + projectUuid?: Prisma.SortOrderInput | Prisma.SortOrder + projectId?: Prisma.SortOrder + userId?: Prisma.SortOrder + permission?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + _count?: Prisma.ProjectMemberCountOrderByAggregateInput + _avg?: Prisma.ProjectMemberAvgOrderByAggregateInput + _max?: Prisma.ProjectMemberMaxOrderByAggregateInput + _min?: Prisma.ProjectMemberMinOrderByAggregateInput + _sum?: Prisma.ProjectMemberSumOrderByAggregateInput +} + +export type ProjectMemberScalarWhereWithAggregatesInput = { + AND?: Prisma.ProjectMemberScalarWhereWithAggregatesInput | Prisma.ProjectMemberScalarWhereWithAggregatesInput[] + OR?: Prisma.ProjectMemberScalarWhereWithAggregatesInput[] + NOT?: Prisma.ProjectMemberScalarWhereWithAggregatesInput | Prisma.ProjectMemberScalarWhereWithAggregatesInput[] + id?: Prisma.IntWithAggregatesFilter<"ProjectMember"> | number + uuid?: Prisma.StringWithAggregatesFilter<"ProjectMember"> | string + projectUuid?: Prisma.StringNullableWithAggregatesFilter<"ProjectMember"> | string | null + projectId?: Prisma.IntWithAggregatesFilter<"ProjectMember"> | number + userId?: Prisma.IntWithAggregatesFilter<"ProjectMember"> | number + permission?: Prisma.StringWithAggregatesFilter<"ProjectMember"> | string + createdAt?: Prisma.DateTimeWithAggregatesFilter<"ProjectMember"> | Date | string +} + +export type ProjectMemberCreateInput = { + uuid: string + projectUuid?: string | null + permission?: string + createdAt?: Date | string + project: Prisma.ProjectCreateNestedOneWithoutMembersInput + user: Prisma.UserCreateNestedOneWithoutProjectMembersInput +} + +export type ProjectMemberUncheckedCreateInput = { + id?: number + uuid: string + projectUuid?: string | null + projectId: number + userId: number + permission?: string + createdAt?: Date | string +} + +export type ProjectMemberUpdateInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + permission?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + project?: Prisma.ProjectUpdateOneRequiredWithoutMembersNestedInput + user?: Prisma.UserUpdateOneRequiredWithoutProjectMembersNestedInput +} + +export type ProjectMemberUncheckedUpdateInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.IntFieldUpdateOperationsInput | number + permission?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type ProjectMemberCreateManyInput = { + id?: number + uuid: string + projectUuid?: string | null + projectId: number + userId: number + permission?: string + createdAt?: Date | string +} + +export type ProjectMemberUpdateManyMutationInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + permission?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type ProjectMemberUncheckedUpdateManyInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.IntFieldUpdateOperationsInput | number + permission?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type ProjectMemberListRelationFilter = { + every?: Prisma.ProjectMemberWhereInput + some?: Prisma.ProjectMemberWhereInput + none?: Prisma.ProjectMemberWhereInput +} + +export type ProjectMemberOrderByRelationAggregateInput = { + _count?: Prisma.SortOrder +} + +export type ProjectMemberProjectIdUserIdCompoundUniqueInput = { + projectId: number + userId: number +} + +export type ProjectMemberCountOrderByAggregateInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + projectUuid?: Prisma.SortOrder + projectId?: Prisma.SortOrder + userId?: Prisma.SortOrder + permission?: Prisma.SortOrder + createdAt?: Prisma.SortOrder +} + +export type ProjectMemberAvgOrderByAggregateInput = { + id?: Prisma.SortOrder + projectId?: Prisma.SortOrder + userId?: Prisma.SortOrder +} + +export type ProjectMemberMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + projectUuid?: Prisma.SortOrder + projectId?: Prisma.SortOrder + userId?: Prisma.SortOrder + permission?: Prisma.SortOrder + createdAt?: Prisma.SortOrder +} + +export type ProjectMemberMinOrderByAggregateInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + projectUuid?: Prisma.SortOrder + projectId?: Prisma.SortOrder + userId?: Prisma.SortOrder + permission?: Prisma.SortOrder + createdAt?: Prisma.SortOrder +} + +export type ProjectMemberSumOrderByAggregateInput = { + id?: Prisma.SortOrder + projectId?: Prisma.SortOrder + userId?: Prisma.SortOrder +} + +export type ProjectMemberCreateNestedManyWithoutUserInput = { + create?: Prisma.XOR | Prisma.ProjectMemberCreateWithoutUserInput[] | Prisma.ProjectMemberUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.ProjectMemberCreateOrConnectWithoutUserInput | Prisma.ProjectMemberCreateOrConnectWithoutUserInput[] + createMany?: Prisma.ProjectMemberCreateManyUserInputEnvelope + connect?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] +} + +export type ProjectMemberUncheckedCreateNestedManyWithoutUserInput = { + create?: Prisma.XOR | Prisma.ProjectMemberCreateWithoutUserInput[] | Prisma.ProjectMemberUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.ProjectMemberCreateOrConnectWithoutUserInput | Prisma.ProjectMemberCreateOrConnectWithoutUserInput[] + createMany?: Prisma.ProjectMemberCreateManyUserInputEnvelope + connect?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] +} + +export type ProjectMemberUpdateManyWithoutUserNestedInput = { + create?: Prisma.XOR | Prisma.ProjectMemberCreateWithoutUserInput[] | Prisma.ProjectMemberUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.ProjectMemberCreateOrConnectWithoutUserInput | Prisma.ProjectMemberCreateOrConnectWithoutUserInput[] + upsert?: Prisma.ProjectMemberUpsertWithWhereUniqueWithoutUserInput | Prisma.ProjectMemberUpsertWithWhereUniqueWithoutUserInput[] + createMany?: Prisma.ProjectMemberCreateManyUserInputEnvelope + set?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] + disconnect?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] + delete?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] + connect?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] + update?: Prisma.ProjectMemberUpdateWithWhereUniqueWithoutUserInput | Prisma.ProjectMemberUpdateWithWhereUniqueWithoutUserInput[] + updateMany?: Prisma.ProjectMemberUpdateManyWithWhereWithoutUserInput | Prisma.ProjectMemberUpdateManyWithWhereWithoutUserInput[] + deleteMany?: Prisma.ProjectMemberScalarWhereInput | Prisma.ProjectMemberScalarWhereInput[] +} + +export type ProjectMemberUncheckedUpdateManyWithoutUserNestedInput = { + create?: Prisma.XOR | Prisma.ProjectMemberCreateWithoutUserInput[] | Prisma.ProjectMemberUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.ProjectMemberCreateOrConnectWithoutUserInput | Prisma.ProjectMemberCreateOrConnectWithoutUserInput[] + upsert?: Prisma.ProjectMemberUpsertWithWhereUniqueWithoutUserInput | Prisma.ProjectMemberUpsertWithWhereUniqueWithoutUserInput[] + createMany?: Prisma.ProjectMemberCreateManyUserInputEnvelope + set?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] + disconnect?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] + delete?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] + connect?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] + update?: Prisma.ProjectMemberUpdateWithWhereUniqueWithoutUserInput | Prisma.ProjectMemberUpdateWithWhereUniqueWithoutUserInput[] + updateMany?: Prisma.ProjectMemberUpdateManyWithWhereWithoutUserInput | Prisma.ProjectMemberUpdateManyWithWhereWithoutUserInput[] + deleteMany?: Prisma.ProjectMemberScalarWhereInput | Prisma.ProjectMemberScalarWhereInput[] +} + +export type ProjectMemberCreateNestedManyWithoutProjectInput = { + create?: Prisma.XOR | Prisma.ProjectMemberCreateWithoutProjectInput[] | Prisma.ProjectMemberUncheckedCreateWithoutProjectInput[] + connectOrCreate?: Prisma.ProjectMemberCreateOrConnectWithoutProjectInput | Prisma.ProjectMemberCreateOrConnectWithoutProjectInput[] + createMany?: Prisma.ProjectMemberCreateManyProjectInputEnvelope + connect?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] +} + +export type ProjectMemberUncheckedCreateNestedManyWithoutProjectInput = { + create?: Prisma.XOR | Prisma.ProjectMemberCreateWithoutProjectInput[] | Prisma.ProjectMemberUncheckedCreateWithoutProjectInput[] + connectOrCreate?: Prisma.ProjectMemberCreateOrConnectWithoutProjectInput | Prisma.ProjectMemberCreateOrConnectWithoutProjectInput[] + createMany?: Prisma.ProjectMemberCreateManyProjectInputEnvelope + connect?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] +} + +export type ProjectMemberUpdateManyWithoutProjectNestedInput = { + create?: Prisma.XOR | Prisma.ProjectMemberCreateWithoutProjectInput[] | Prisma.ProjectMemberUncheckedCreateWithoutProjectInput[] + connectOrCreate?: Prisma.ProjectMemberCreateOrConnectWithoutProjectInput | Prisma.ProjectMemberCreateOrConnectWithoutProjectInput[] + upsert?: Prisma.ProjectMemberUpsertWithWhereUniqueWithoutProjectInput | Prisma.ProjectMemberUpsertWithWhereUniqueWithoutProjectInput[] + createMany?: Prisma.ProjectMemberCreateManyProjectInputEnvelope + set?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] + disconnect?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] + delete?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] + connect?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] + update?: Prisma.ProjectMemberUpdateWithWhereUniqueWithoutProjectInput | Prisma.ProjectMemberUpdateWithWhereUniqueWithoutProjectInput[] + updateMany?: Prisma.ProjectMemberUpdateManyWithWhereWithoutProjectInput | Prisma.ProjectMemberUpdateManyWithWhereWithoutProjectInput[] + deleteMany?: Prisma.ProjectMemberScalarWhereInput | Prisma.ProjectMemberScalarWhereInput[] +} + +export type ProjectMemberUncheckedUpdateManyWithoutProjectNestedInput = { + create?: Prisma.XOR | Prisma.ProjectMemberCreateWithoutProjectInput[] | Prisma.ProjectMemberUncheckedCreateWithoutProjectInput[] + connectOrCreate?: Prisma.ProjectMemberCreateOrConnectWithoutProjectInput | Prisma.ProjectMemberCreateOrConnectWithoutProjectInput[] + upsert?: Prisma.ProjectMemberUpsertWithWhereUniqueWithoutProjectInput | Prisma.ProjectMemberUpsertWithWhereUniqueWithoutProjectInput[] + createMany?: Prisma.ProjectMemberCreateManyProjectInputEnvelope + set?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] + disconnect?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] + delete?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] + connect?: Prisma.ProjectMemberWhereUniqueInput | Prisma.ProjectMemberWhereUniqueInput[] + update?: Prisma.ProjectMemberUpdateWithWhereUniqueWithoutProjectInput | Prisma.ProjectMemberUpdateWithWhereUniqueWithoutProjectInput[] + updateMany?: Prisma.ProjectMemberUpdateManyWithWhereWithoutProjectInput | Prisma.ProjectMemberUpdateManyWithWhereWithoutProjectInput[] + deleteMany?: Prisma.ProjectMemberScalarWhereInput | Prisma.ProjectMemberScalarWhereInput[] +} + +export type ProjectMemberCreateWithoutUserInput = { + uuid: string + projectUuid?: string | null + permission?: string + createdAt?: Date | string + project: Prisma.ProjectCreateNestedOneWithoutMembersInput +} + +export type ProjectMemberUncheckedCreateWithoutUserInput = { + id?: number + uuid: string + projectUuid?: string | null + projectId: number + permission?: string + createdAt?: Date | string +} + +export type ProjectMemberCreateOrConnectWithoutUserInput = { + where: Prisma.ProjectMemberWhereUniqueInput + create: Prisma.XOR +} + +export type ProjectMemberCreateManyUserInputEnvelope = { + data: Prisma.ProjectMemberCreateManyUserInput | Prisma.ProjectMemberCreateManyUserInput[] + skipDuplicates?: boolean +} + +export type ProjectMemberUpsertWithWhereUniqueWithoutUserInput = { + where: Prisma.ProjectMemberWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type ProjectMemberUpdateWithWhereUniqueWithoutUserInput = { + where: Prisma.ProjectMemberWhereUniqueInput + data: Prisma.XOR +} + +export type ProjectMemberUpdateManyWithWhereWithoutUserInput = { + where: Prisma.ProjectMemberScalarWhereInput + data: Prisma.XOR +} + +export type ProjectMemberScalarWhereInput = { + AND?: Prisma.ProjectMemberScalarWhereInput | Prisma.ProjectMemberScalarWhereInput[] + OR?: Prisma.ProjectMemberScalarWhereInput[] + NOT?: Prisma.ProjectMemberScalarWhereInput | Prisma.ProjectMemberScalarWhereInput[] + id?: Prisma.IntFilter<"ProjectMember"> | number + uuid?: Prisma.StringFilter<"ProjectMember"> | string + projectUuid?: Prisma.StringNullableFilter<"ProjectMember"> | string | null + projectId?: Prisma.IntFilter<"ProjectMember"> | number + userId?: Prisma.IntFilter<"ProjectMember"> | number + permission?: Prisma.StringFilter<"ProjectMember"> | string + createdAt?: Prisma.DateTimeFilter<"ProjectMember"> | Date | string +} + +export type ProjectMemberCreateWithoutProjectInput = { + uuid: string + projectUuid?: string | null + permission?: string + createdAt?: Date | string + user: Prisma.UserCreateNestedOneWithoutProjectMembersInput +} + +export type ProjectMemberUncheckedCreateWithoutProjectInput = { + id?: number + uuid: string + projectUuid?: string | null + userId: number + permission?: string + createdAt?: Date | string +} + +export type ProjectMemberCreateOrConnectWithoutProjectInput = { + where: Prisma.ProjectMemberWhereUniqueInput + create: Prisma.XOR +} + +export type ProjectMemberCreateManyProjectInputEnvelope = { + data: Prisma.ProjectMemberCreateManyProjectInput | Prisma.ProjectMemberCreateManyProjectInput[] + skipDuplicates?: boolean +} + +export type ProjectMemberUpsertWithWhereUniqueWithoutProjectInput = { + where: Prisma.ProjectMemberWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type ProjectMemberUpdateWithWhereUniqueWithoutProjectInput = { + where: Prisma.ProjectMemberWhereUniqueInput + data: Prisma.XOR +} + +export type ProjectMemberUpdateManyWithWhereWithoutProjectInput = { + where: Prisma.ProjectMemberScalarWhereInput + data: Prisma.XOR +} + +export type ProjectMemberCreateManyUserInput = { + id?: number + uuid: string + projectUuid?: string | null + projectId: number + permission?: string + createdAt?: Date | string +} + +export type ProjectMemberUpdateWithoutUserInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + permission?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + project?: Prisma.ProjectUpdateOneRequiredWithoutMembersNestedInput +} + +export type ProjectMemberUncheckedUpdateWithoutUserInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + permission?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type ProjectMemberUncheckedUpdateManyWithoutUserInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + permission?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type ProjectMemberCreateManyProjectInput = { + id?: number + uuid: string + projectUuid?: string | null + userId: number + permission?: string + createdAt?: Date | string +} + +export type ProjectMemberUpdateWithoutProjectInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + permission?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + user?: Prisma.UserUpdateOneRequiredWithoutProjectMembersNestedInput +} + +export type ProjectMemberUncheckedUpdateWithoutProjectInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + userId?: Prisma.IntFieldUpdateOperationsInput | number + permission?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type ProjectMemberUncheckedUpdateManyWithoutProjectInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + userId?: Prisma.IntFieldUpdateOperationsInput | number + permission?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + + + +export type ProjectMemberSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + projectUuid?: boolean + projectId?: boolean + userId?: boolean + permission?: boolean + createdAt?: boolean + project?: boolean | Prisma.ProjectDefaultArgs + user?: boolean | Prisma.UserDefaultArgs +}, ExtArgs["result"]["projectMember"]> + +export type ProjectMemberSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + projectUuid?: boolean + projectId?: boolean + userId?: boolean + permission?: boolean + createdAt?: boolean + project?: boolean | Prisma.ProjectDefaultArgs + user?: boolean | Prisma.UserDefaultArgs +}, ExtArgs["result"]["projectMember"]> + +export type ProjectMemberSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + projectUuid?: boolean + projectId?: boolean + userId?: boolean + permission?: boolean + createdAt?: boolean + project?: boolean | Prisma.ProjectDefaultArgs + user?: boolean | Prisma.UserDefaultArgs +}, ExtArgs["result"]["projectMember"]> + +export type ProjectMemberSelectScalar = { + id?: boolean + uuid?: boolean + projectUuid?: boolean + projectId?: boolean + userId?: boolean + permission?: boolean + createdAt?: boolean +} + +export type ProjectMemberOmit = runtime.Types.Extensions.GetOmit<"id" | "uuid" | "projectUuid" | "projectId" | "userId" | "permission" | "createdAt", ExtArgs["result"]["projectMember"]> +export type ProjectMemberInclude = { + project?: boolean | Prisma.ProjectDefaultArgs + user?: boolean | Prisma.UserDefaultArgs +} +export type ProjectMemberIncludeCreateManyAndReturn = { + project?: boolean | Prisma.ProjectDefaultArgs + user?: boolean | Prisma.UserDefaultArgs +} +export type ProjectMemberIncludeUpdateManyAndReturn = { + project?: boolean | Prisma.ProjectDefaultArgs + user?: boolean | Prisma.UserDefaultArgs +} + +export type $ProjectMemberPayload = { + name: "ProjectMember" + objects: { + project: Prisma.$ProjectPayload + user: Prisma.$UserPayload + } + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: number + uuid: string + projectUuid: string | null + projectId: number + userId: number + permission: string + createdAt: Date + }, ExtArgs["result"]["projectMember"]> + composites: {} +} + +export type ProjectMemberGetPayload = runtime.Types.Result.GetResult + +export type ProjectMemberCountArgs = + Omit & { + select?: ProjectMemberCountAggregateInputType | true + } + +export interface ProjectMemberDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['ProjectMember'], meta: { name: 'ProjectMember' } } + /** + * Find zero or one ProjectMember that matches the filter. + * @param {ProjectMemberFindUniqueArgs} args - Arguments to find a ProjectMember + * @example + * // Get one ProjectMember + * const projectMember = await prisma.projectMember.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__ProjectMemberClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one ProjectMember that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {ProjectMemberFindUniqueOrThrowArgs} args - Arguments to find a ProjectMember + * @example + * // Get one ProjectMember + * const projectMember = await prisma.projectMember.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__ProjectMemberClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first ProjectMember that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ProjectMemberFindFirstArgs} args - Arguments to find a ProjectMember + * @example + * // Get one ProjectMember + * const projectMember = await prisma.projectMember.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__ProjectMemberClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first ProjectMember that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ProjectMemberFindFirstOrThrowArgs} args - Arguments to find a ProjectMember + * @example + * // Get one ProjectMember + * const projectMember = await prisma.projectMember.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__ProjectMemberClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more ProjectMembers that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ProjectMemberFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all ProjectMembers + * const projectMembers = await prisma.projectMember.findMany() + * + * // Get first 10 ProjectMembers + * const projectMembers = await prisma.projectMember.findMany({ take: 10 }) + * + * // Only select the `id` + * const projectMemberWithIdOnly = await prisma.projectMember.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a ProjectMember. + * @param {ProjectMemberCreateArgs} args - Arguments to create a ProjectMember. + * @example + * // Create one ProjectMember + * const ProjectMember = await prisma.projectMember.create({ + * data: { + * // ... data to create a ProjectMember + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__ProjectMemberClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many ProjectMembers. + * @param {ProjectMemberCreateManyArgs} args - Arguments to create many ProjectMembers. + * @example + * // Create many ProjectMembers + * const projectMember = await prisma.projectMember.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many ProjectMembers and returns the data saved in the database. + * @param {ProjectMemberCreateManyAndReturnArgs} args - Arguments to create many ProjectMembers. + * @example + * // Create many ProjectMembers + * const projectMember = await prisma.projectMember.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many ProjectMembers and only return the `id` + * const projectMemberWithIdOnly = await prisma.projectMember.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a ProjectMember. + * @param {ProjectMemberDeleteArgs} args - Arguments to delete one ProjectMember. + * @example + * // Delete one ProjectMember + * const ProjectMember = await prisma.projectMember.delete({ + * where: { + * // ... filter to delete one ProjectMember + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__ProjectMemberClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one ProjectMember. + * @param {ProjectMemberUpdateArgs} args - Arguments to update one ProjectMember. + * @example + * // Update one ProjectMember + * const projectMember = await prisma.projectMember.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__ProjectMemberClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more ProjectMembers. + * @param {ProjectMemberDeleteManyArgs} args - Arguments to filter ProjectMembers to delete. + * @example + * // Delete a few ProjectMembers + * const { count } = await prisma.projectMember.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more ProjectMembers. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ProjectMemberUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many ProjectMembers + * const projectMember = await prisma.projectMember.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more ProjectMembers and returns the data updated in the database. + * @param {ProjectMemberUpdateManyAndReturnArgs} args - Arguments to update many ProjectMembers. + * @example + * // Update many ProjectMembers + * const projectMember = await prisma.projectMember.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more ProjectMembers and only return the `id` + * const projectMemberWithIdOnly = await prisma.projectMember.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one ProjectMember. + * @param {ProjectMemberUpsertArgs} args - Arguments to update or create a ProjectMember. + * @example + * // Update or create a ProjectMember + * const projectMember = await prisma.projectMember.upsert({ + * create: { + * // ... data to create a ProjectMember + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the ProjectMember we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__ProjectMemberClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of ProjectMembers. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ProjectMemberCountArgs} args - Arguments to filter ProjectMembers to count. + * @example + * // Count the number of ProjectMembers + * const count = await prisma.projectMember.count({ + * where: { + * // ... the filter for the ProjectMembers we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a ProjectMember. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ProjectMemberAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by ProjectMember. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {ProjectMemberGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends ProjectMemberGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: ProjectMemberGroupByArgs['orderBy'] } + : { orderBy?: ProjectMemberGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetProjectMemberGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the ProjectMember model + */ +readonly fields: ProjectMemberFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for ProjectMember. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__ProjectMemberClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + project = {}>(args?: Prisma.Subset>): Prisma.Prisma__ProjectClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + user = {}>(args?: Prisma.Subset>): Prisma.Prisma__UserClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the ProjectMember model + */ +export interface ProjectMemberFieldRefs { + readonly id: Prisma.FieldRef<"ProjectMember", 'Int'> + readonly uuid: Prisma.FieldRef<"ProjectMember", 'String'> + readonly projectUuid: Prisma.FieldRef<"ProjectMember", 'String'> + readonly projectId: Prisma.FieldRef<"ProjectMember", 'Int'> + readonly userId: Prisma.FieldRef<"ProjectMember", 'Int'> + readonly permission: Prisma.FieldRef<"ProjectMember", 'String'> + readonly createdAt: Prisma.FieldRef<"ProjectMember", 'DateTime'> +} + + +// Custom InputTypes +/** + * ProjectMember findUnique + */ +export type ProjectMemberFindUniqueArgs = { + /** + * Select specific fields to fetch from the ProjectMember + */ + select?: Prisma.ProjectMemberSelect | null + /** + * Omit specific fields from the ProjectMember + */ + omit?: Prisma.ProjectMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectMemberInclude | null + /** + * Filter, which ProjectMember to fetch. + */ + where: Prisma.ProjectMemberWhereUniqueInput +} + +/** + * ProjectMember findUniqueOrThrow + */ +export type ProjectMemberFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the ProjectMember + */ + select?: Prisma.ProjectMemberSelect | null + /** + * Omit specific fields from the ProjectMember + */ + omit?: Prisma.ProjectMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectMemberInclude | null + /** + * Filter, which ProjectMember to fetch. + */ + where: Prisma.ProjectMemberWhereUniqueInput +} + +/** + * ProjectMember findFirst + */ +export type ProjectMemberFindFirstArgs = { + /** + * Select specific fields to fetch from the ProjectMember + */ + select?: Prisma.ProjectMemberSelect | null + /** + * Omit specific fields from the ProjectMember + */ + omit?: Prisma.ProjectMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectMemberInclude | null + /** + * Filter, which ProjectMember to fetch. + */ + where?: Prisma.ProjectMemberWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of ProjectMembers to fetch. + */ + orderBy?: Prisma.ProjectMemberOrderByWithRelationInput | Prisma.ProjectMemberOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for ProjectMembers. + */ + cursor?: Prisma.ProjectMemberWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` ProjectMembers from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` ProjectMembers. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of ProjectMembers. + */ + distinct?: Prisma.ProjectMemberScalarFieldEnum | Prisma.ProjectMemberScalarFieldEnum[] +} + +/** + * ProjectMember findFirstOrThrow + */ +export type ProjectMemberFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the ProjectMember + */ + select?: Prisma.ProjectMemberSelect | null + /** + * Omit specific fields from the ProjectMember + */ + omit?: Prisma.ProjectMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectMemberInclude | null + /** + * Filter, which ProjectMember to fetch. + */ + where?: Prisma.ProjectMemberWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of ProjectMembers to fetch. + */ + orderBy?: Prisma.ProjectMemberOrderByWithRelationInput | Prisma.ProjectMemberOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for ProjectMembers. + */ + cursor?: Prisma.ProjectMemberWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` ProjectMembers from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` ProjectMembers. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of ProjectMembers. + */ + distinct?: Prisma.ProjectMemberScalarFieldEnum | Prisma.ProjectMemberScalarFieldEnum[] +} + +/** + * ProjectMember findMany + */ +export type ProjectMemberFindManyArgs = { + /** + * Select specific fields to fetch from the ProjectMember + */ + select?: Prisma.ProjectMemberSelect | null + /** + * Omit specific fields from the ProjectMember + */ + omit?: Prisma.ProjectMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectMemberInclude | null + /** + * Filter, which ProjectMembers to fetch. + */ + where?: Prisma.ProjectMemberWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of ProjectMembers to fetch. + */ + orderBy?: Prisma.ProjectMemberOrderByWithRelationInput | Prisma.ProjectMemberOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing ProjectMembers. + */ + cursor?: Prisma.ProjectMemberWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` ProjectMembers from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` ProjectMembers. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of ProjectMembers. + */ + distinct?: Prisma.ProjectMemberScalarFieldEnum | Prisma.ProjectMemberScalarFieldEnum[] +} + +/** + * ProjectMember create + */ +export type ProjectMemberCreateArgs = { + /** + * Select specific fields to fetch from the ProjectMember + */ + select?: Prisma.ProjectMemberSelect | null + /** + * Omit specific fields from the ProjectMember + */ + omit?: Prisma.ProjectMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectMemberInclude | null + /** + * The data needed to create a ProjectMember. + */ + data: Prisma.XOR +} + +/** + * ProjectMember createMany + */ +export type ProjectMemberCreateManyArgs = { + /** + * The data used to create many ProjectMembers. + */ + data: Prisma.ProjectMemberCreateManyInput | Prisma.ProjectMemberCreateManyInput[] + skipDuplicates?: boolean +} + +/** + * ProjectMember createManyAndReturn + */ +export type ProjectMemberCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the ProjectMember + */ + select?: Prisma.ProjectMemberSelectCreateManyAndReturn | null + /** + * Omit specific fields from the ProjectMember + */ + omit?: Prisma.ProjectMemberOmit | null + /** + * The data used to create many ProjectMembers. + */ + data: Prisma.ProjectMemberCreateManyInput | Prisma.ProjectMemberCreateManyInput[] + skipDuplicates?: boolean + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectMemberIncludeCreateManyAndReturn | null +} + +/** + * ProjectMember update + */ +export type ProjectMemberUpdateArgs = { + /** + * Select specific fields to fetch from the ProjectMember + */ + select?: Prisma.ProjectMemberSelect | null + /** + * Omit specific fields from the ProjectMember + */ + omit?: Prisma.ProjectMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectMemberInclude | null + /** + * The data needed to update a ProjectMember. + */ + data: Prisma.XOR + /** + * Choose, which ProjectMember to update. + */ + where: Prisma.ProjectMemberWhereUniqueInput +} + +/** + * ProjectMember updateMany + */ +export type ProjectMemberUpdateManyArgs = { + /** + * The data used to update ProjectMembers. + */ + data: Prisma.XOR + /** + * Filter which ProjectMembers to update + */ + where?: Prisma.ProjectMemberWhereInput + /** + * Limit how many ProjectMembers to update. + */ + limit?: number +} + +/** + * ProjectMember updateManyAndReturn + */ +export type ProjectMemberUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the ProjectMember + */ + select?: Prisma.ProjectMemberSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the ProjectMember + */ + omit?: Prisma.ProjectMemberOmit | null + /** + * The data used to update ProjectMembers. + */ + data: Prisma.XOR + /** + * Filter which ProjectMembers to update + */ + where?: Prisma.ProjectMemberWhereInput + /** + * Limit how many ProjectMembers to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectMemberIncludeUpdateManyAndReturn | null +} + +/** + * ProjectMember upsert + */ +export type ProjectMemberUpsertArgs = { + /** + * Select specific fields to fetch from the ProjectMember + */ + select?: Prisma.ProjectMemberSelect | null + /** + * Omit specific fields from the ProjectMember + */ + omit?: Prisma.ProjectMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectMemberInclude | null + /** + * The filter to search for the ProjectMember to update in case it exists. + */ + where: Prisma.ProjectMemberWhereUniqueInput + /** + * In case the ProjectMember found by the `where` argument doesn't exist, create a new ProjectMember with this data. + */ + create: Prisma.XOR + /** + * In case the ProjectMember was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * ProjectMember delete + */ +export type ProjectMemberDeleteArgs = { + /** + * Select specific fields to fetch from the ProjectMember + */ + select?: Prisma.ProjectMemberSelect | null + /** + * Omit specific fields from the ProjectMember + */ + omit?: Prisma.ProjectMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectMemberInclude | null + /** + * Filter which ProjectMember to delete. + */ + where: Prisma.ProjectMemberWhereUniqueInput +} + +/** + * ProjectMember deleteMany + */ +export type ProjectMemberDeleteManyArgs = { + /** + * Filter which ProjectMembers to delete + */ + where?: Prisma.ProjectMemberWhereInput + /** + * Limit how many ProjectMembers to delete. + */ + limit?: number +} + +/** + * ProjectMember without action + */ +export type ProjectMemberDefaultArgs = { + /** + * Select specific fields to fetch from the ProjectMember + */ + select?: Prisma.ProjectMemberSelect | null + /** + * Omit specific fields from the ProjectMember + */ + omit?: Prisma.ProjectMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectMemberInclude | null +} diff --git a/generated/prisma/models/ScraperCache.ts b/generated/prisma/models/ScraperCache.ts index 3affd10..6bf0eec 100644 --- a/generated/prisma/models/ScraperCache.ts +++ b/generated/prisma/models/ScraperCache.ts @@ -238,7 +238,6 @@ export type ScraperCacheOrderByWithRelationInput = { metadata?: Prisma.SortOrderInput | Prisma.SortOrder createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder - _relevance?: Prisma.ScraperCacheOrderByRelevanceInput } export type ScraperCacheWhereUniqueInput = Prisma.AtLeast<{ @@ -349,12 +348,6 @@ export type ScraperCacheUncheckedUpdateManyInput = { updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string } -export type ScraperCacheOrderByRelevanceInput = { - fields: Prisma.ScraperCacheOrderByRelevanceFieldEnum | Prisma.ScraperCacheOrderByRelevanceFieldEnum[] - sort: Prisma.SortOrder - search: string -} - export type ScraperCacheCountOrderByAggregateInput = { id?: Prisma.SortOrder scraperName?: Prisma.SortOrder @@ -403,7 +396,25 @@ export type ScraperCacheSelect +export type ScraperCacheSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + scraperName?: boolean + lastDataHash?: boolean + lastUpdateAt?: boolean + metadata?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["scraperCache"]> +export type ScraperCacheSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + scraperName?: boolean + lastDataHash?: boolean + lastUpdateAt?: boolean + metadata?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["scraperCache"]> export type ScraperCacheSelectScalar = { id?: boolean @@ -545,6 +556,30 @@ export interface ScraperCacheDelegate(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Create many ScraperCaches and returns the data saved in the database. + * @param {ScraperCacheCreateManyAndReturnArgs} args - Arguments to create many ScraperCaches. + * @example + * // Create many ScraperCaches + * const scraperCache = await prisma.scraperCache.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many ScraperCaches and only return the `id` + * const scraperCacheWithIdOnly = await prisma.scraperCache.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + /** * Delete a ScraperCache. * @param {ScraperCacheDeleteArgs} args - Arguments to delete one ScraperCache. @@ -609,6 +644,36 @@ export interface ScraperCacheDelegate(args: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Update zero or more ScraperCaches and returns the data updated in the database. + * @param {ScraperCacheUpdateManyAndReturnArgs} args - Arguments to update many ScraperCaches. + * @example + * // Update many ScraperCaches + * const scraperCache = await prisma.scraperCache.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more ScraperCaches and only return the `id` + * const scraperCacheWithIdOnly = await prisma.scraperCache.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + /** * Create or update one ScraperCache. * @param {ScraperCacheUpsertArgs} args - Arguments to update or create a ScraperCache. @@ -1017,6 +1082,25 @@ export type ScraperCacheCreateManyArgs = { + /** + * Select specific fields to fetch from the ScraperCache + */ + select?: Prisma.ScraperCacheSelectCreateManyAndReturn | null + /** + * Omit specific fields from the ScraperCache + */ + omit?: Prisma.ScraperCacheOmit | null + /** + * The data used to create many ScraperCaches. + */ + data: Prisma.ScraperCacheCreateManyInput | Prisma.ScraperCacheCreateManyInput[] + skipDuplicates?: boolean +} + /** * ScraperCache update */ @@ -1057,6 +1141,32 @@ export type ScraperCacheUpdateManyArgs = { + /** + * Select specific fields to fetch from the ScraperCache + */ + select?: Prisma.ScraperCacheSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the ScraperCache + */ + omit?: Prisma.ScraperCacheOmit | null + /** + * The data used to update ScraperCaches. + */ + data: Prisma.XOR + /** + * Filter which ScraperCaches to update + */ + where?: Prisma.ScraperCacheWhereInput + /** + * Limit how many ScraperCaches to update. + */ + limit?: number +} + /** * ScraperCache upsert */ diff --git a/generated/prisma/models/ServerNode.ts b/generated/prisma/models/ServerNode.ts index 78ad387..65e59e5 100644 --- a/generated/prisma/models/ServerNode.ts +++ b/generated/prisma/models/ServerNode.ts @@ -464,7 +464,6 @@ export type ServerNodeOrderByWithRelationInput = { recordedAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder - _relevance?: Prisma.ServerNodeOrderByRelevanceInput } export type ServerNodeWhereUniqueInput = Prisma.AtLeast<{ @@ -755,12 +754,6 @@ export type ServerNodeUncheckedUpdateManyInput = { updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string } -export type ServerNodeOrderByRelevanceInput = { - fields: Prisma.ServerNodeOrderByRelevanceFieldEnum | Prisma.ServerNodeOrderByRelevanceFieldEnum[] - sort: Prisma.SortOrder - search: string -} - export type ServerNodeCountOrderByAggregateInput = { id?: Prisma.SortOrder uuid?: Prisma.SortOrder @@ -921,7 +914,61 @@ export type ServerNodeSelect +export type ServerNodeSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + name?: boolean + cpuName?: boolean + os?: boolean + region?: boolean + memTotal?: boolean + diskTotal?: boolean + cpuCores?: boolean + cpuUsage?: boolean + ramUsed?: boolean + swapUsed?: boolean + load1?: boolean + load5?: boolean + load15?: boolean + diskUsed?: boolean + netIn?: boolean + netOut?: boolean + uptime?: boolean + process?: boolean + connections?: boolean + status?: boolean + recordedAt?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["serverNode"]> +export type ServerNodeSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + name?: boolean + cpuName?: boolean + os?: boolean + region?: boolean + memTotal?: boolean + diskTotal?: boolean + cpuCores?: boolean + cpuUsage?: boolean + ramUsed?: boolean + swapUsed?: boolean + load1?: boolean + load5?: boolean + load15?: boolean + diskUsed?: boolean + netIn?: boolean + netOut?: boolean + uptime?: boolean + process?: boolean + connections?: boolean + status?: boolean + recordedAt?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["serverNode"]> export type ServerNodeSelectScalar = { id?: boolean @@ -1099,6 +1146,30 @@ export interface ServerNodeDelegate(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Create many ServerNodes and returns the data saved in the database. + * @param {ServerNodeCreateManyAndReturnArgs} args - Arguments to create many ServerNodes. + * @example + * // Create many ServerNodes + * const serverNode = await prisma.serverNode.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many ServerNodes and only return the `id` + * const serverNodeWithIdOnly = await prisma.serverNode.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + /** * Delete a ServerNode. * @param {ServerNodeDeleteArgs} args - Arguments to delete one ServerNode. @@ -1163,6 +1234,36 @@ export interface ServerNodeDelegate(args: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Update zero or more ServerNodes and returns the data updated in the database. + * @param {ServerNodeUpdateManyAndReturnArgs} args - Arguments to update many ServerNodes. + * @example + * // Update many ServerNodes + * const serverNode = await prisma.serverNode.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more ServerNodes and only return the `id` + * const serverNodeWithIdOnly = await prisma.serverNode.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + /** * Create or update one ServerNode. * @param {ServerNodeUpsertArgs} args - Arguments to update or create a ServerNode. @@ -1589,6 +1690,25 @@ export type ServerNodeCreateManyArgs = { + /** + * Select specific fields to fetch from the ServerNode + */ + select?: Prisma.ServerNodeSelectCreateManyAndReturn | null + /** + * Omit specific fields from the ServerNode + */ + omit?: Prisma.ServerNodeOmit | null + /** + * The data used to create many ServerNodes. + */ + data: Prisma.ServerNodeCreateManyInput | Prisma.ServerNodeCreateManyInput[] + skipDuplicates?: boolean +} + /** * ServerNode update */ @@ -1629,6 +1749,32 @@ export type ServerNodeUpdateManyArgs = { + /** + * Select specific fields to fetch from the ServerNode + */ + select?: Prisma.ServerNodeSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the ServerNode + */ + omit?: Prisma.ServerNodeOmit | null + /** + * The data used to update ServerNodes. + */ + data: Prisma.XOR + /** + * Filter which ServerNodes to update + */ + where?: Prisma.ServerNodeWhereInput + /** + * Limit how many ServerNodes to update. + */ + limit?: number +} + /** * ServerNode upsert */ diff --git a/generated/prisma/models/ServerNodeHistory.ts b/generated/prisma/models/ServerNodeHistory.ts index 23cb1dc..433dd75 100644 --- a/generated/prisma/models/ServerNodeHistory.ts +++ b/generated/prisma/models/ServerNodeHistory.ts @@ -319,7 +319,6 @@ export type ServerNodeHistoryOrderByWithRelationInput = { process?: Prisma.SortOrder connections?: Prisma.SortOrder createdAt?: Prisma.SortOrder - _relevance?: Prisma.ServerNodeHistoryOrderByRelevanceInput } export type ServerNodeHistoryWhereUniqueInput = Prisma.AtLeast<{ @@ -481,12 +480,6 @@ export type ServerNodeHistoryUncheckedUpdateManyInput = { createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string } -export type ServerNodeHistoryOrderByRelevanceInput = { - fields: Prisma.ServerNodeHistoryOrderByRelevanceFieldEnum | Prisma.ServerNodeHistoryOrderByRelevanceFieldEnum[] - sort: Prisma.SortOrder - search: string -} - export type ServerNodeHistoryUuidPeriodCompoundUniqueInput = { uuid: string period: Date | string @@ -578,7 +571,35 @@ export type ServerNodeHistorySelect +export type ServerNodeHistorySelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + period?: boolean + cpu?: boolean + ram?: boolean + load?: boolean + disk?: boolean + netIn?: boolean + netOut?: boolean + process?: boolean + connections?: boolean + createdAt?: boolean +}, ExtArgs["result"]["serverNodeHistory"]> +export type ServerNodeHistorySelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + period?: boolean + cpu?: boolean + ram?: boolean + load?: boolean + disk?: boolean + netIn?: boolean + netOut?: boolean + process?: boolean + connections?: boolean + createdAt?: boolean +}, ExtArgs["result"]["serverNodeHistory"]> export type ServerNodeHistorySelectScalar = { id?: boolean @@ -730,6 +751,30 @@ export interface ServerNodeHistoryDelegate(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Create many ServerNodeHistories and returns the data saved in the database. + * @param {ServerNodeHistoryCreateManyAndReturnArgs} args - Arguments to create many ServerNodeHistories. + * @example + * // Create many ServerNodeHistories + * const serverNodeHistory = await prisma.serverNodeHistory.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many ServerNodeHistories and only return the `id` + * const serverNodeHistoryWithIdOnly = await prisma.serverNodeHistory.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + /** * Delete a ServerNodeHistory. * @param {ServerNodeHistoryDeleteArgs} args - Arguments to delete one ServerNodeHistory. @@ -794,6 +839,36 @@ export interface ServerNodeHistoryDelegate(args: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Update zero or more ServerNodeHistories and returns the data updated in the database. + * @param {ServerNodeHistoryUpdateManyAndReturnArgs} args - Arguments to update many ServerNodeHistories. + * @example + * // Update many ServerNodeHistories + * const serverNodeHistory = await prisma.serverNodeHistory.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more ServerNodeHistories and only return the `id` + * const serverNodeHistoryWithIdOnly = await prisma.serverNodeHistory.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + /** * Create or update one ServerNodeHistory. * @param {ServerNodeHistoryUpsertArgs} args - Arguments to update or create a ServerNodeHistory. @@ -1207,6 +1282,25 @@ export type ServerNodeHistoryCreateManyArgs = { + /** + * Select specific fields to fetch from the ServerNodeHistory + */ + select?: Prisma.ServerNodeHistorySelectCreateManyAndReturn | null + /** + * Omit specific fields from the ServerNodeHistory + */ + omit?: Prisma.ServerNodeHistoryOmit | null + /** + * The data used to create many ServerNodeHistories. + */ + data: Prisma.ServerNodeHistoryCreateManyInput | Prisma.ServerNodeHistoryCreateManyInput[] + skipDuplicates?: boolean +} + /** * ServerNodeHistory update */ @@ -1247,6 +1341,32 @@ export type ServerNodeHistoryUpdateManyArgs = { + /** + * Select specific fields to fetch from the ServerNodeHistory + */ + select?: Prisma.ServerNodeHistorySelectUpdateManyAndReturn | null + /** + * Omit specific fields from the ServerNodeHistory + */ + omit?: Prisma.ServerNodeHistoryOmit | null + /** + * The data used to update ServerNodeHistories. + */ + data: Prisma.XOR + /** + * Filter which ServerNodeHistories to update + */ + where?: Prisma.ServerNodeHistoryWhereInput + /** + * Limit how many ServerNodeHistories to update. + */ + limit?: number +} + /** * ServerNodeHistory upsert */ diff --git a/generated/prisma/models/ServiceCheck.ts b/generated/prisma/models/ServiceCheck.ts index 2dcc2eb..9723311 100644 --- a/generated/prisma/models/ServiceCheck.ts +++ b/generated/prisma/models/ServiceCheck.ts @@ -264,7 +264,6 @@ export type ServiceCheckOrderByWithRelationInput = { lastCheckAt?: Prisma.SortOrder createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder - _relevance?: Prisma.ServiceCheckOrderByRelevanceInput } export type ServiceCheckWhereUniqueInput = Prisma.AtLeast<{ @@ -395,12 +394,6 @@ export type ServiceCheckUncheckedUpdateManyInput = { updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string } -export type ServiceCheckOrderByRelevanceInput = { - fields: Prisma.ServiceCheckOrderByRelevanceFieldEnum | Prisma.ServiceCheckOrderByRelevanceFieldEnum[] - sort: Prisma.SortOrder - search: string -} - export type ServiceCheckCountOrderByAggregateInput = { id?: Prisma.SortOrder name?: Prisma.SortOrder @@ -461,7 +454,29 @@ export type ServiceCheckSelect +export type ServiceCheckSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + name?: boolean + url?: boolean + status?: boolean + latencyMs?: boolean + message?: boolean + lastCheckAt?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["serviceCheck"]> +export type ServiceCheckSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + name?: boolean + url?: boolean + status?: boolean + latencyMs?: boolean + message?: boolean + lastCheckAt?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["serviceCheck"]> export type ServiceCheckSelectScalar = { id?: boolean @@ -607,6 +622,30 @@ export interface ServiceCheckDelegate(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Create many ServiceChecks and returns the data saved in the database. + * @param {ServiceCheckCreateManyAndReturnArgs} args - Arguments to create many ServiceChecks. + * @example + * // Create many ServiceChecks + * const serviceCheck = await prisma.serviceCheck.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many ServiceChecks and only return the `id` + * const serviceCheckWithIdOnly = await prisma.serviceCheck.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + /** * Delete a ServiceCheck. * @param {ServiceCheckDeleteArgs} args - Arguments to delete one ServiceCheck. @@ -671,6 +710,36 @@ export interface ServiceCheckDelegate(args: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Update zero or more ServiceChecks and returns the data updated in the database. + * @param {ServiceCheckUpdateManyAndReturnArgs} args - Arguments to update many ServiceChecks. + * @example + * // Update many ServiceChecks + * const serviceCheck = await prisma.serviceCheck.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more ServiceChecks and only return the `id` + * const serviceCheckWithIdOnly = await prisma.serviceCheck.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + /** * Create or update one ServiceCheck. * @param {ServiceCheckUpsertArgs} args - Arguments to update or create a ServiceCheck. @@ -1081,6 +1150,25 @@ export type ServiceCheckCreateManyArgs = { + /** + * Select specific fields to fetch from the ServiceCheck + */ + select?: Prisma.ServiceCheckSelectCreateManyAndReturn | null + /** + * Omit specific fields from the ServiceCheck + */ + omit?: Prisma.ServiceCheckOmit | null + /** + * The data used to create many ServiceChecks. + */ + data: Prisma.ServiceCheckCreateManyInput | Prisma.ServiceCheckCreateManyInput[] + skipDuplicates?: boolean +} + /** * ServiceCheck update */ @@ -1121,6 +1209,32 @@ export type ServiceCheckUpdateManyArgs = { + /** + * Select specific fields to fetch from the ServiceCheck + */ + select?: Prisma.ServiceCheckSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the ServiceCheck + */ + omit?: Prisma.ServiceCheckOmit | null + /** + * The data used to update ServiceChecks. + */ + data: Prisma.XOR + /** + * Filter which ServiceChecks to update + */ + where?: Prisma.ServiceCheckWhereInput + /** + * Limit how many ServiceChecks to update. + */ + limit?: number +} + /** * ServiceCheck upsert */ diff --git a/generated/prisma/models/Strategy.ts b/generated/prisma/models/Strategy.ts index b08f5ab..36383c5 100644 --- a/generated/prisma/models/Strategy.ts +++ b/generated/prisma/models/Strategy.ts @@ -229,7 +229,6 @@ export type StrategyOrderByWithRelationInput = { data?: Prisma.SortOrder createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder - _relevance?: Prisma.StrategyOrderByRelevanceInput } export type StrategyWhereUniqueInput = Prisma.AtLeast<{ @@ -330,12 +329,6 @@ export type StrategyUncheckedUpdateManyInput = { updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string } -export type StrategyOrderByRelevanceInput = { - fields: Prisma.StrategyOrderByRelevanceFieldEnum | Prisma.StrategyOrderByRelevanceFieldEnum[] - sort: Prisma.SortOrder - search: string -} - export type StrategyCountOrderByAggregateInput = { id?: Prisma.SortOrder name?: Prisma.SortOrder @@ -380,7 +373,23 @@ export type StrategySelect +export type StrategySelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + name?: boolean + description?: boolean + data?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["strategy"]> +export type StrategySelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + name?: boolean + description?: boolean + data?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["strategy"]> export type StrategySelectScalar = { id?: boolean @@ -520,6 +529,30 @@ export interface StrategyDelegate(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Create many Strategies and returns the data saved in the database. + * @param {StrategyCreateManyAndReturnArgs} args - Arguments to create many Strategies. + * @example + * // Create many Strategies + * const strategy = await prisma.strategy.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many Strategies and only return the `id` + * const strategyWithIdOnly = await prisma.strategy.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + /** * Delete a Strategy. * @param {StrategyDeleteArgs} args - Arguments to delete one Strategy. @@ -584,6 +617,36 @@ export interface StrategyDelegate(args: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Update zero or more Strategies and returns the data updated in the database. + * @param {StrategyUpdateManyAndReturnArgs} args - Arguments to update many Strategies. + * @example + * // Update many Strategies + * const strategy = await prisma.strategy.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more Strategies and only return the `id` + * const strategyWithIdOnly = await prisma.strategy.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + /** * Create or update one Strategy. * @param {StrategyUpsertArgs} args - Arguments to update or create a Strategy. @@ -991,6 +1054,25 @@ export type StrategyCreateManyArgs = { + /** + * Select specific fields to fetch from the Strategy + */ + select?: Prisma.StrategySelectCreateManyAndReturn | null + /** + * Omit specific fields from the Strategy + */ + omit?: Prisma.StrategyOmit | null + /** + * The data used to create many Strategies. + */ + data: Prisma.StrategyCreateManyInput | Prisma.StrategyCreateManyInput[] + skipDuplicates?: boolean +} + /** * Strategy update */ @@ -1031,6 +1113,32 @@ export type StrategyUpdateManyArgs = { + /** + * Select specific fields to fetch from the Strategy + */ + select?: Prisma.StrategySelectUpdateManyAndReturn | null + /** + * Omit specific fields from the Strategy + */ + omit?: Prisma.StrategyOmit | null + /** + * The data used to update Strategies. + */ + data: Prisma.XOR + /** + * Filter which Strategies to update + */ + where?: Prisma.StrategyWhereInput + /** + * Limit how many Strategies to update. + */ + limit?: number +} + /** * Strategy upsert */ diff --git a/generated/prisma/models/Task.ts b/generated/prisma/models/Task.ts new file mode 100644 index 0000000..14f5126 --- /dev/null +++ b/generated/prisma/models/Task.ts @@ -0,0 +1,3715 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `Task` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums.ts" +import type * as Prisma from "../internal/prismaNamespace.ts" + +/** + * Model Task + * + */ +export type TaskModel = runtime.Types.Result.DefaultSelection + +export type AggregateTask = { + _count: TaskCountAggregateOutputType | null + _avg: TaskAvgAggregateOutputType | null + _sum: TaskSumAggregateOutputType | null + _min: TaskMinAggregateOutputType | null + _max: TaskMaxAggregateOutputType | null +} + +export type TaskAvgAggregateOutputType = { + id: number | null + projectId: number | null + columnId: number | null + parentId: number | null + assigneeId: number | null + creatorId: number | null + sortOrder: number | null + number: number | null +} + +export type TaskSumAggregateOutputType = { + id: number | null + projectId: number | null + columnId: number | null + parentId: number | null + assigneeId: number | null + creatorId: number | null + sortOrder: number | null + number: number | null +} + +export type TaskMinAggregateOutputType = { + id: number | null + uuid: string | null + projectUuid: string | null + columnUuid: string | null + parentUuid: string | null + projectId: number | null + columnId: number | null + parentId: number | null + title: string | null + description: string | null + kind: string | null + markDate: Date | null + status: string | null + doneAt: Date | null + assigneeId: number | null + creatorId: number | null + sortOrder: number | null + archived: boolean | null + archivedAt: Date | null + archiveSource: string | null + createdAt: Date | null + updatedAt: Date | null + number: number | null +} + +export type TaskMaxAggregateOutputType = { + id: number | null + uuid: string | null + projectUuid: string | null + columnUuid: string | null + parentUuid: string | null + projectId: number | null + columnId: number | null + parentId: number | null + title: string | null + description: string | null + kind: string | null + markDate: Date | null + status: string | null + doneAt: Date | null + assigneeId: number | null + creatorId: number | null + sortOrder: number | null + archived: boolean | null + archivedAt: Date | null + archiveSource: string | null + createdAt: Date | null + updatedAt: Date | null + number: number | null +} + +export type TaskCountAggregateOutputType = { + id: number + uuid: number + projectUuid: number + columnUuid: number + parentUuid: number + projectId: number + columnId: number + parentId: number + title: number + description: number + kind: number + markDate: number + status: number + doneAt: number + assigneeId: number + creatorId: number + extras: number + sortOrder: number + archived: number + archivedAt: number + archiveSource: number + createdAt: number + updatedAt: number + number: number + _all: number +} + + +export type TaskAvgAggregateInputType = { + id?: true + projectId?: true + columnId?: true + parentId?: true + assigneeId?: true + creatorId?: true + sortOrder?: true + number?: true +} + +export type TaskSumAggregateInputType = { + id?: true + projectId?: true + columnId?: true + parentId?: true + assigneeId?: true + creatorId?: true + sortOrder?: true + number?: true +} + +export type TaskMinAggregateInputType = { + id?: true + uuid?: true + projectUuid?: true + columnUuid?: true + parentUuid?: true + projectId?: true + columnId?: true + parentId?: true + title?: true + description?: true + kind?: true + markDate?: true + status?: true + doneAt?: true + assigneeId?: true + creatorId?: true + sortOrder?: true + archived?: true + archivedAt?: true + archiveSource?: true + createdAt?: true + updatedAt?: true + number?: true +} + +export type TaskMaxAggregateInputType = { + id?: true + uuid?: true + projectUuid?: true + columnUuid?: true + parentUuid?: true + projectId?: true + columnId?: true + parentId?: true + title?: true + description?: true + kind?: true + markDate?: true + status?: true + doneAt?: true + assigneeId?: true + creatorId?: true + sortOrder?: true + archived?: true + archivedAt?: true + archiveSource?: true + createdAt?: true + updatedAt?: true + number?: true +} + +export type TaskCountAggregateInputType = { + id?: true + uuid?: true + projectUuid?: true + columnUuid?: true + parentUuid?: true + projectId?: true + columnId?: true + parentId?: true + title?: true + description?: true + kind?: true + markDate?: true + status?: true + doneAt?: true + assigneeId?: true + creatorId?: true + extras?: true + sortOrder?: true + archived?: true + archivedAt?: true + archiveSource?: true + createdAt?: true + updatedAt?: true + number?: true + _all?: true +} + +export type TaskAggregateArgs = { + /** + * Filter which Task to aggregate. + */ + where?: Prisma.TaskWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Tasks to fetch. + */ + orderBy?: Prisma.TaskOrderByWithRelationInput | Prisma.TaskOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.TaskWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Tasks from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Tasks. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned Tasks + **/ + _count?: true | TaskCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to average + **/ + _avg?: TaskAvgAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to sum + **/ + _sum?: TaskSumAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: TaskMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: TaskMaxAggregateInputType +} + +export type GetTaskAggregateType = { + [P in keyof T & keyof AggregateTask]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type TaskGroupByArgs = { + where?: Prisma.TaskWhereInput + orderBy?: Prisma.TaskOrderByWithAggregationInput | Prisma.TaskOrderByWithAggregationInput[] + by: Prisma.TaskScalarFieldEnum[] | Prisma.TaskScalarFieldEnum + having?: Prisma.TaskScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: TaskCountAggregateInputType | true + _avg?: TaskAvgAggregateInputType + _sum?: TaskSumAggregateInputType + _min?: TaskMinAggregateInputType + _max?: TaskMaxAggregateInputType +} + +export type TaskGroupByOutputType = { + id: number + uuid: string + projectUuid: string | null + columnUuid: string | null + parentUuid: string | null + projectId: number + columnId: number + parentId: number | null + title: string + description: string + kind: string | null + markDate: Date | null + status: string + doneAt: Date | null + assigneeId: number | null + creatorId: number + extras: runtime.JsonValue + sortOrder: number + archived: boolean + archivedAt: Date | null + archiveSource: string | null + createdAt: Date + updatedAt: Date + number: number | null + _count: TaskCountAggregateOutputType | null + _avg: TaskAvgAggregateOutputType | null + _sum: TaskSumAggregateOutputType | null + _min: TaskMinAggregateOutputType | null + _max: TaskMaxAggregateOutputType | null +} + +export type GetTaskGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof TaskGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type TaskWhereInput = { + AND?: Prisma.TaskWhereInput | Prisma.TaskWhereInput[] + OR?: Prisma.TaskWhereInput[] + NOT?: Prisma.TaskWhereInput | Prisma.TaskWhereInput[] + id?: Prisma.IntFilter<"Task"> | number + uuid?: Prisma.StringFilter<"Task"> | string + projectUuid?: Prisma.StringNullableFilter<"Task"> | string | null + columnUuid?: Prisma.StringNullableFilter<"Task"> | string | null + parentUuid?: Prisma.StringNullableFilter<"Task"> | string | null + projectId?: Prisma.IntFilter<"Task"> | number + columnId?: Prisma.IntFilter<"Task"> | number + parentId?: Prisma.IntNullableFilter<"Task"> | number | null + title?: Prisma.StringFilter<"Task"> | string + description?: Prisma.StringFilter<"Task"> | string + kind?: Prisma.StringNullableFilter<"Task"> | string | null + markDate?: Prisma.DateTimeNullableFilter<"Task"> | Date | string | null + status?: Prisma.StringFilter<"Task"> | string + doneAt?: Prisma.DateTimeNullableFilter<"Task"> | Date | string | null + assigneeId?: Prisma.IntNullableFilter<"Task"> | number | null + creatorId?: Prisma.IntFilter<"Task"> | number + extras?: Prisma.JsonFilter<"Task"> + sortOrder?: Prisma.IntFilter<"Task"> | number + archived?: Prisma.BoolFilter<"Task"> | boolean + archivedAt?: Prisma.DateTimeNullableFilter<"Task"> | Date | string | null + archiveSource?: Prisma.StringNullableFilter<"Task"> | string | null + createdAt?: Prisma.DateTimeFilter<"Task"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"Task"> | Date | string + number?: Prisma.IntNullableFilter<"Task"> | number | null + project?: Prisma.XOR + column?: Prisma.XOR + parent?: Prisma.XOR | null + children?: Prisma.TaskListRelationFilter + assignee?: Prisma.XOR | null + creator?: Prisma.XOR + members?: Prisma.TaskMemberListRelationFilter + tags?: Prisma.TaskTagListRelationFilter +} + +export type TaskOrderByWithRelationInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + projectUuid?: Prisma.SortOrderInput | Prisma.SortOrder + columnUuid?: Prisma.SortOrderInput | Prisma.SortOrder + parentUuid?: Prisma.SortOrderInput | Prisma.SortOrder + projectId?: Prisma.SortOrder + columnId?: Prisma.SortOrder + parentId?: Prisma.SortOrderInput | Prisma.SortOrder + title?: Prisma.SortOrder + description?: Prisma.SortOrder + kind?: Prisma.SortOrderInput | Prisma.SortOrder + markDate?: Prisma.SortOrderInput | Prisma.SortOrder + status?: Prisma.SortOrder + doneAt?: Prisma.SortOrderInput | Prisma.SortOrder + assigneeId?: Prisma.SortOrderInput | Prisma.SortOrder + creatorId?: Prisma.SortOrder + extras?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + archived?: Prisma.SortOrder + archivedAt?: Prisma.SortOrderInput | Prisma.SortOrder + archiveSource?: Prisma.SortOrderInput | Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + number?: Prisma.SortOrderInput | Prisma.SortOrder + project?: Prisma.ProjectOrderByWithRelationInput + column?: Prisma.BoardColumnOrderByWithRelationInput + parent?: Prisma.TaskOrderByWithRelationInput + children?: Prisma.TaskOrderByRelationAggregateInput + assignee?: Prisma.UserOrderByWithRelationInput + creator?: Prisma.UserOrderByWithRelationInput + members?: Prisma.TaskMemberOrderByRelationAggregateInput + tags?: Prisma.TaskTagOrderByRelationAggregateInput +} + +export type TaskWhereUniqueInput = Prisma.AtLeast<{ + id?: number + uuid?: string + projectId_number?: Prisma.TaskProjectIdNumberCompoundUniqueInput + AND?: Prisma.TaskWhereInput | Prisma.TaskWhereInput[] + OR?: Prisma.TaskWhereInput[] + NOT?: Prisma.TaskWhereInput | Prisma.TaskWhereInput[] + projectUuid?: Prisma.StringNullableFilter<"Task"> | string | null + columnUuid?: Prisma.StringNullableFilter<"Task"> | string | null + parentUuid?: Prisma.StringNullableFilter<"Task"> | string | null + projectId?: Prisma.IntFilter<"Task"> | number + columnId?: Prisma.IntFilter<"Task"> | number + parentId?: Prisma.IntNullableFilter<"Task"> | number | null + title?: Prisma.StringFilter<"Task"> | string + description?: Prisma.StringFilter<"Task"> | string + kind?: Prisma.StringNullableFilter<"Task"> | string | null + markDate?: Prisma.DateTimeNullableFilter<"Task"> | Date | string | null + status?: Prisma.StringFilter<"Task"> | string + doneAt?: Prisma.DateTimeNullableFilter<"Task"> | Date | string | null + assigneeId?: Prisma.IntNullableFilter<"Task"> | number | null + creatorId?: Prisma.IntFilter<"Task"> | number + extras?: Prisma.JsonFilter<"Task"> + sortOrder?: Prisma.IntFilter<"Task"> | number + archived?: Prisma.BoolFilter<"Task"> | boolean + archivedAt?: Prisma.DateTimeNullableFilter<"Task"> | Date | string | null + archiveSource?: Prisma.StringNullableFilter<"Task"> | string | null + createdAt?: Prisma.DateTimeFilter<"Task"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"Task"> | Date | string + number?: Prisma.IntNullableFilter<"Task"> | number | null + project?: Prisma.XOR + column?: Prisma.XOR + parent?: Prisma.XOR | null + children?: Prisma.TaskListRelationFilter + assignee?: Prisma.XOR | null + creator?: Prisma.XOR + members?: Prisma.TaskMemberListRelationFilter + tags?: Prisma.TaskTagListRelationFilter +}, "id" | "uuid" | "projectId_number"> + +export type TaskOrderByWithAggregationInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + projectUuid?: Prisma.SortOrderInput | Prisma.SortOrder + columnUuid?: Prisma.SortOrderInput | Prisma.SortOrder + parentUuid?: Prisma.SortOrderInput | Prisma.SortOrder + projectId?: Prisma.SortOrder + columnId?: Prisma.SortOrder + parentId?: Prisma.SortOrderInput | Prisma.SortOrder + title?: Prisma.SortOrder + description?: Prisma.SortOrder + kind?: Prisma.SortOrderInput | Prisma.SortOrder + markDate?: Prisma.SortOrderInput | Prisma.SortOrder + status?: Prisma.SortOrder + doneAt?: Prisma.SortOrderInput | Prisma.SortOrder + assigneeId?: Prisma.SortOrderInput | Prisma.SortOrder + creatorId?: Prisma.SortOrder + extras?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + archived?: Prisma.SortOrder + archivedAt?: Prisma.SortOrderInput | Prisma.SortOrder + archiveSource?: Prisma.SortOrderInput | Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + number?: Prisma.SortOrderInput | Prisma.SortOrder + _count?: Prisma.TaskCountOrderByAggregateInput + _avg?: Prisma.TaskAvgOrderByAggregateInput + _max?: Prisma.TaskMaxOrderByAggregateInput + _min?: Prisma.TaskMinOrderByAggregateInput + _sum?: Prisma.TaskSumOrderByAggregateInput +} + +export type TaskScalarWhereWithAggregatesInput = { + AND?: Prisma.TaskScalarWhereWithAggregatesInput | Prisma.TaskScalarWhereWithAggregatesInput[] + OR?: Prisma.TaskScalarWhereWithAggregatesInput[] + NOT?: Prisma.TaskScalarWhereWithAggregatesInput | Prisma.TaskScalarWhereWithAggregatesInput[] + id?: Prisma.IntWithAggregatesFilter<"Task"> | number + uuid?: Prisma.StringWithAggregatesFilter<"Task"> | string + projectUuid?: Prisma.StringNullableWithAggregatesFilter<"Task"> | string | null + columnUuid?: Prisma.StringNullableWithAggregatesFilter<"Task"> | string | null + parentUuid?: Prisma.StringNullableWithAggregatesFilter<"Task"> | string | null + projectId?: Prisma.IntWithAggregatesFilter<"Task"> | number + columnId?: Prisma.IntWithAggregatesFilter<"Task"> | number + parentId?: Prisma.IntNullableWithAggregatesFilter<"Task"> | number | null + title?: Prisma.StringWithAggregatesFilter<"Task"> | string + description?: Prisma.StringWithAggregatesFilter<"Task"> | string + kind?: Prisma.StringNullableWithAggregatesFilter<"Task"> | string | null + markDate?: Prisma.DateTimeNullableWithAggregatesFilter<"Task"> | Date | string | null + status?: Prisma.StringWithAggregatesFilter<"Task"> | string + doneAt?: Prisma.DateTimeNullableWithAggregatesFilter<"Task"> | Date | string | null + assigneeId?: Prisma.IntNullableWithAggregatesFilter<"Task"> | number | null + creatorId?: Prisma.IntWithAggregatesFilter<"Task"> | number + extras?: Prisma.JsonWithAggregatesFilter<"Task"> + sortOrder?: Prisma.IntWithAggregatesFilter<"Task"> | number + archived?: Prisma.BoolWithAggregatesFilter<"Task"> | boolean + archivedAt?: Prisma.DateTimeNullableWithAggregatesFilter<"Task"> | Date | string | null + archiveSource?: Prisma.StringNullableWithAggregatesFilter<"Task"> | string | null + createdAt?: Prisma.DateTimeWithAggregatesFilter<"Task"> | Date | string + updatedAt?: Prisma.DateTimeWithAggregatesFilter<"Task"> | Date | string + number?: Prisma.IntNullableWithAggregatesFilter<"Task"> | number | null +} + +export type TaskCreateInput = { + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null + project: Prisma.ProjectCreateNestedOneWithoutTasksInput + column: Prisma.BoardColumnCreateNestedOneWithoutTasksInput + parent?: Prisma.TaskCreateNestedOneWithoutChildrenInput + children?: Prisma.TaskCreateNestedManyWithoutParentInput + assignee?: Prisma.UserCreateNestedOneWithoutAssignedTasksInput + creator: Prisma.UserCreateNestedOneWithoutCreatedTasksInput + members?: Prisma.TaskMemberCreateNestedManyWithoutTaskInput + tags?: Prisma.TaskTagCreateNestedManyWithoutTaskInput +} + +export type TaskUncheckedCreateInput = { + id?: number + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + projectId: number + columnId: number + parentId?: number | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + assigneeId?: number | null + creatorId: number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null + children?: Prisma.TaskUncheckedCreateNestedManyWithoutParentInput + members?: Prisma.TaskMemberUncheckedCreateNestedManyWithoutTaskInput + tags?: Prisma.TaskTagUncheckedCreateNestedManyWithoutTaskInput +} + +export type TaskUpdateInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + project?: Prisma.ProjectUpdateOneRequiredWithoutTasksNestedInput + column?: Prisma.BoardColumnUpdateOneRequiredWithoutTasksNestedInput + parent?: Prisma.TaskUpdateOneWithoutChildrenNestedInput + children?: Prisma.TaskUpdateManyWithoutParentNestedInput + assignee?: Prisma.UserUpdateOneWithoutAssignedTasksNestedInput + creator?: Prisma.UserUpdateOneRequiredWithoutCreatedTasksNestedInput + members?: Prisma.TaskMemberUpdateManyWithoutTaskNestedInput + tags?: Prisma.TaskTagUpdateManyWithoutTaskNestedInput +} + +export type TaskUncheckedUpdateInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + columnId?: Prisma.IntFieldUpdateOperationsInput | number + parentId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + assigneeId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + creatorId?: Prisma.IntFieldUpdateOperationsInput | number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + children?: Prisma.TaskUncheckedUpdateManyWithoutParentNestedInput + members?: Prisma.TaskMemberUncheckedUpdateManyWithoutTaskNestedInput + tags?: Prisma.TaskTagUncheckedUpdateManyWithoutTaskNestedInput +} + +export type TaskCreateManyInput = { + id?: number + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + projectId: number + columnId: number + parentId?: number | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + assigneeId?: number | null + creatorId: number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null +} + +export type TaskUpdateManyMutationInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null +} + +export type TaskUncheckedUpdateManyInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + columnId?: Prisma.IntFieldUpdateOperationsInput | number + parentId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + assigneeId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + creatorId?: Prisma.IntFieldUpdateOperationsInput | number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null +} + +export type TaskListRelationFilter = { + every?: Prisma.TaskWhereInput + some?: Prisma.TaskWhereInput + none?: Prisma.TaskWhereInput +} + +export type TaskOrderByRelationAggregateInput = { + _count?: Prisma.SortOrder +} + +export type TaskNullableScalarRelationFilter = { + is?: Prisma.TaskWhereInput | null + isNot?: Prisma.TaskWhereInput | null +} + +export type TaskProjectIdNumberCompoundUniqueInput = { + projectId: number + number: number +} + +export type TaskCountOrderByAggregateInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + projectUuid?: Prisma.SortOrder + columnUuid?: Prisma.SortOrder + parentUuid?: Prisma.SortOrder + projectId?: Prisma.SortOrder + columnId?: Prisma.SortOrder + parentId?: Prisma.SortOrder + title?: Prisma.SortOrder + description?: Prisma.SortOrder + kind?: Prisma.SortOrder + markDate?: Prisma.SortOrder + status?: Prisma.SortOrder + doneAt?: Prisma.SortOrder + assigneeId?: Prisma.SortOrder + creatorId?: Prisma.SortOrder + extras?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + archived?: Prisma.SortOrder + archivedAt?: Prisma.SortOrder + archiveSource?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + number?: Prisma.SortOrder +} + +export type TaskAvgOrderByAggregateInput = { + id?: Prisma.SortOrder + projectId?: Prisma.SortOrder + columnId?: Prisma.SortOrder + parentId?: Prisma.SortOrder + assigneeId?: Prisma.SortOrder + creatorId?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + number?: Prisma.SortOrder +} + +export type TaskMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + projectUuid?: Prisma.SortOrder + columnUuid?: Prisma.SortOrder + parentUuid?: Prisma.SortOrder + projectId?: Prisma.SortOrder + columnId?: Prisma.SortOrder + parentId?: Prisma.SortOrder + title?: Prisma.SortOrder + description?: Prisma.SortOrder + kind?: Prisma.SortOrder + markDate?: Prisma.SortOrder + status?: Prisma.SortOrder + doneAt?: Prisma.SortOrder + assigneeId?: Prisma.SortOrder + creatorId?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + archived?: Prisma.SortOrder + archivedAt?: Prisma.SortOrder + archiveSource?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + number?: Prisma.SortOrder +} + +export type TaskMinOrderByAggregateInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + projectUuid?: Prisma.SortOrder + columnUuid?: Prisma.SortOrder + parentUuid?: Prisma.SortOrder + projectId?: Prisma.SortOrder + columnId?: Prisma.SortOrder + parentId?: Prisma.SortOrder + title?: Prisma.SortOrder + description?: Prisma.SortOrder + kind?: Prisma.SortOrder + markDate?: Prisma.SortOrder + status?: Prisma.SortOrder + doneAt?: Prisma.SortOrder + assigneeId?: Prisma.SortOrder + creatorId?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + archived?: Prisma.SortOrder + archivedAt?: Prisma.SortOrder + archiveSource?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + number?: Prisma.SortOrder +} + +export type TaskSumOrderByAggregateInput = { + id?: Prisma.SortOrder + projectId?: Prisma.SortOrder + columnId?: Prisma.SortOrder + parentId?: Prisma.SortOrder + assigneeId?: Prisma.SortOrder + creatorId?: Prisma.SortOrder + sortOrder?: Prisma.SortOrder + number?: Prisma.SortOrder +} + +export type TaskScalarRelationFilter = { + is?: Prisma.TaskWhereInput + isNot?: Prisma.TaskWhereInput +} + +export type TaskCreateNestedManyWithoutAssigneeInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutAssigneeInput[] | Prisma.TaskUncheckedCreateWithoutAssigneeInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutAssigneeInput | Prisma.TaskCreateOrConnectWithoutAssigneeInput[] + createMany?: Prisma.TaskCreateManyAssigneeInputEnvelope + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] +} + +export type TaskCreateNestedManyWithoutCreatorInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutCreatorInput[] | Prisma.TaskUncheckedCreateWithoutCreatorInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutCreatorInput | Prisma.TaskCreateOrConnectWithoutCreatorInput[] + createMany?: Prisma.TaskCreateManyCreatorInputEnvelope + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] +} + +export type TaskUncheckedCreateNestedManyWithoutAssigneeInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutAssigneeInput[] | Prisma.TaskUncheckedCreateWithoutAssigneeInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutAssigneeInput | Prisma.TaskCreateOrConnectWithoutAssigneeInput[] + createMany?: Prisma.TaskCreateManyAssigneeInputEnvelope + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] +} + +export type TaskUncheckedCreateNestedManyWithoutCreatorInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutCreatorInput[] | Prisma.TaskUncheckedCreateWithoutCreatorInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutCreatorInput | Prisma.TaskCreateOrConnectWithoutCreatorInput[] + createMany?: Prisma.TaskCreateManyCreatorInputEnvelope + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] +} + +export type TaskUpdateManyWithoutAssigneeNestedInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutAssigneeInput[] | Prisma.TaskUncheckedCreateWithoutAssigneeInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutAssigneeInput | Prisma.TaskCreateOrConnectWithoutAssigneeInput[] + upsert?: Prisma.TaskUpsertWithWhereUniqueWithoutAssigneeInput | Prisma.TaskUpsertWithWhereUniqueWithoutAssigneeInput[] + createMany?: Prisma.TaskCreateManyAssigneeInputEnvelope + set?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + disconnect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + delete?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + update?: Prisma.TaskUpdateWithWhereUniqueWithoutAssigneeInput | Prisma.TaskUpdateWithWhereUniqueWithoutAssigneeInput[] + updateMany?: Prisma.TaskUpdateManyWithWhereWithoutAssigneeInput | Prisma.TaskUpdateManyWithWhereWithoutAssigneeInput[] + deleteMany?: Prisma.TaskScalarWhereInput | Prisma.TaskScalarWhereInput[] +} + +export type TaskUpdateManyWithoutCreatorNestedInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutCreatorInput[] | Prisma.TaskUncheckedCreateWithoutCreatorInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutCreatorInput | Prisma.TaskCreateOrConnectWithoutCreatorInput[] + upsert?: Prisma.TaskUpsertWithWhereUniqueWithoutCreatorInput | Prisma.TaskUpsertWithWhereUniqueWithoutCreatorInput[] + createMany?: Prisma.TaskCreateManyCreatorInputEnvelope + set?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + disconnect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + delete?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + update?: Prisma.TaskUpdateWithWhereUniqueWithoutCreatorInput | Prisma.TaskUpdateWithWhereUniqueWithoutCreatorInput[] + updateMany?: Prisma.TaskUpdateManyWithWhereWithoutCreatorInput | Prisma.TaskUpdateManyWithWhereWithoutCreatorInput[] + deleteMany?: Prisma.TaskScalarWhereInput | Prisma.TaskScalarWhereInput[] +} + +export type TaskUncheckedUpdateManyWithoutAssigneeNestedInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutAssigneeInput[] | Prisma.TaskUncheckedCreateWithoutAssigneeInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutAssigneeInput | Prisma.TaskCreateOrConnectWithoutAssigneeInput[] + upsert?: Prisma.TaskUpsertWithWhereUniqueWithoutAssigneeInput | Prisma.TaskUpsertWithWhereUniqueWithoutAssigneeInput[] + createMany?: Prisma.TaskCreateManyAssigneeInputEnvelope + set?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + disconnect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + delete?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + update?: Prisma.TaskUpdateWithWhereUniqueWithoutAssigneeInput | Prisma.TaskUpdateWithWhereUniqueWithoutAssigneeInput[] + updateMany?: Prisma.TaskUpdateManyWithWhereWithoutAssigneeInput | Prisma.TaskUpdateManyWithWhereWithoutAssigneeInput[] + deleteMany?: Prisma.TaskScalarWhereInput | Prisma.TaskScalarWhereInput[] +} + +export type TaskUncheckedUpdateManyWithoutCreatorNestedInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutCreatorInput[] | Prisma.TaskUncheckedCreateWithoutCreatorInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutCreatorInput | Prisma.TaskCreateOrConnectWithoutCreatorInput[] + upsert?: Prisma.TaskUpsertWithWhereUniqueWithoutCreatorInput | Prisma.TaskUpsertWithWhereUniqueWithoutCreatorInput[] + createMany?: Prisma.TaskCreateManyCreatorInputEnvelope + set?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + disconnect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + delete?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + update?: Prisma.TaskUpdateWithWhereUniqueWithoutCreatorInput | Prisma.TaskUpdateWithWhereUniqueWithoutCreatorInput[] + updateMany?: Prisma.TaskUpdateManyWithWhereWithoutCreatorInput | Prisma.TaskUpdateManyWithWhereWithoutCreatorInput[] + deleteMany?: Prisma.TaskScalarWhereInput | Prisma.TaskScalarWhereInput[] +} + +export type TaskCreateNestedManyWithoutProjectInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutProjectInput[] | Prisma.TaskUncheckedCreateWithoutProjectInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutProjectInput | Prisma.TaskCreateOrConnectWithoutProjectInput[] + createMany?: Prisma.TaskCreateManyProjectInputEnvelope + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] +} + +export type TaskUncheckedCreateNestedManyWithoutProjectInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutProjectInput[] | Prisma.TaskUncheckedCreateWithoutProjectInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutProjectInput | Prisma.TaskCreateOrConnectWithoutProjectInput[] + createMany?: Prisma.TaskCreateManyProjectInputEnvelope + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] +} + +export type TaskUpdateManyWithoutProjectNestedInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutProjectInput[] | Prisma.TaskUncheckedCreateWithoutProjectInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutProjectInput | Prisma.TaskCreateOrConnectWithoutProjectInput[] + upsert?: Prisma.TaskUpsertWithWhereUniqueWithoutProjectInput | Prisma.TaskUpsertWithWhereUniqueWithoutProjectInput[] + createMany?: Prisma.TaskCreateManyProjectInputEnvelope + set?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + disconnect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + delete?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + update?: Prisma.TaskUpdateWithWhereUniqueWithoutProjectInput | Prisma.TaskUpdateWithWhereUniqueWithoutProjectInput[] + updateMany?: Prisma.TaskUpdateManyWithWhereWithoutProjectInput | Prisma.TaskUpdateManyWithWhereWithoutProjectInput[] + deleteMany?: Prisma.TaskScalarWhereInput | Prisma.TaskScalarWhereInput[] +} + +export type TaskUncheckedUpdateManyWithoutProjectNestedInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutProjectInput[] | Prisma.TaskUncheckedCreateWithoutProjectInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutProjectInput | Prisma.TaskCreateOrConnectWithoutProjectInput[] + upsert?: Prisma.TaskUpsertWithWhereUniqueWithoutProjectInput | Prisma.TaskUpsertWithWhereUniqueWithoutProjectInput[] + createMany?: Prisma.TaskCreateManyProjectInputEnvelope + set?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + disconnect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + delete?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + update?: Prisma.TaskUpdateWithWhereUniqueWithoutProjectInput | Prisma.TaskUpdateWithWhereUniqueWithoutProjectInput[] + updateMany?: Prisma.TaskUpdateManyWithWhereWithoutProjectInput | Prisma.TaskUpdateManyWithWhereWithoutProjectInput[] + deleteMany?: Prisma.TaskScalarWhereInput | Prisma.TaskScalarWhereInput[] +} + +export type TaskCreateNestedManyWithoutColumnInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutColumnInput[] | Prisma.TaskUncheckedCreateWithoutColumnInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutColumnInput | Prisma.TaskCreateOrConnectWithoutColumnInput[] + createMany?: Prisma.TaskCreateManyColumnInputEnvelope + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] +} + +export type TaskUncheckedCreateNestedManyWithoutColumnInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutColumnInput[] | Prisma.TaskUncheckedCreateWithoutColumnInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutColumnInput | Prisma.TaskCreateOrConnectWithoutColumnInput[] + createMany?: Prisma.TaskCreateManyColumnInputEnvelope + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] +} + +export type TaskUpdateManyWithoutColumnNestedInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutColumnInput[] | Prisma.TaskUncheckedCreateWithoutColumnInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutColumnInput | Prisma.TaskCreateOrConnectWithoutColumnInput[] + upsert?: Prisma.TaskUpsertWithWhereUniqueWithoutColumnInput | Prisma.TaskUpsertWithWhereUniqueWithoutColumnInput[] + createMany?: Prisma.TaskCreateManyColumnInputEnvelope + set?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + disconnect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + delete?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + update?: Prisma.TaskUpdateWithWhereUniqueWithoutColumnInput | Prisma.TaskUpdateWithWhereUniqueWithoutColumnInput[] + updateMany?: Prisma.TaskUpdateManyWithWhereWithoutColumnInput | Prisma.TaskUpdateManyWithWhereWithoutColumnInput[] + deleteMany?: Prisma.TaskScalarWhereInput | Prisma.TaskScalarWhereInput[] +} + +export type TaskUncheckedUpdateManyWithoutColumnNestedInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutColumnInput[] | Prisma.TaskUncheckedCreateWithoutColumnInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutColumnInput | Prisma.TaskCreateOrConnectWithoutColumnInput[] + upsert?: Prisma.TaskUpsertWithWhereUniqueWithoutColumnInput | Prisma.TaskUpsertWithWhereUniqueWithoutColumnInput[] + createMany?: Prisma.TaskCreateManyColumnInputEnvelope + set?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + disconnect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + delete?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + update?: Prisma.TaskUpdateWithWhereUniqueWithoutColumnInput | Prisma.TaskUpdateWithWhereUniqueWithoutColumnInput[] + updateMany?: Prisma.TaskUpdateManyWithWhereWithoutColumnInput | Prisma.TaskUpdateManyWithWhereWithoutColumnInput[] + deleteMany?: Prisma.TaskScalarWhereInput | Prisma.TaskScalarWhereInput[] +} + +export type TaskCreateNestedOneWithoutChildrenInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutChildrenInput + connect?: Prisma.TaskWhereUniqueInput +} + +export type TaskCreateNestedManyWithoutParentInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutParentInput[] | Prisma.TaskUncheckedCreateWithoutParentInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutParentInput | Prisma.TaskCreateOrConnectWithoutParentInput[] + createMany?: Prisma.TaskCreateManyParentInputEnvelope + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] +} + +export type TaskUncheckedCreateNestedManyWithoutParentInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutParentInput[] | Prisma.TaskUncheckedCreateWithoutParentInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutParentInput | Prisma.TaskCreateOrConnectWithoutParentInput[] + createMany?: Prisma.TaskCreateManyParentInputEnvelope + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] +} + +export type TaskUpdateOneWithoutChildrenNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutChildrenInput + upsert?: Prisma.TaskUpsertWithoutChildrenInput + disconnect?: Prisma.TaskWhereInput | boolean + delete?: Prisma.TaskWhereInput | boolean + connect?: Prisma.TaskWhereUniqueInput + update?: Prisma.XOR, Prisma.TaskUncheckedUpdateWithoutChildrenInput> +} + +export type TaskUpdateManyWithoutParentNestedInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutParentInput[] | Prisma.TaskUncheckedCreateWithoutParentInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutParentInput | Prisma.TaskCreateOrConnectWithoutParentInput[] + upsert?: Prisma.TaskUpsertWithWhereUniqueWithoutParentInput | Prisma.TaskUpsertWithWhereUniqueWithoutParentInput[] + createMany?: Prisma.TaskCreateManyParentInputEnvelope + set?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + disconnect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + delete?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + update?: Prisma.TaskUpdateWithWhereUniqueWithoutParentInput | Prisma.TaskUpdateWithWhereUniqueWithoutParentInput[] + updateMany?: Prisma.TaskUpdateManyWithWhereWithoutParentInput | Prisma.TaskUpdateManyWithWhereWithoutParentInput[] + deleteMany?: Prisma.TaskScalarWhereInput | Prisma.TaskScalarWhereInput[] +} + +export type TaskUncheckedUpdateManyWithoutParentNestedInput = { + create?: Prisma.XOR | Prisma.TaskCreateWithoutParentInput[] | Prisma.TaskUncheckedCreateWithoutParentInput[] + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutParentInput | Prisma.TaskCreateOrConnectWithoutParentInput[] + upsert?: Prisma.TaskUpsertWithWhereUniqueWithoutParentInput | Prisma.TaskUpsertWithWhereUniqueWithoutParentInput[] + createMany?: Prisma.TaskCreateManyParentInputEnvelope + set?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + disconnect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + delete?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + connect?: Prisma.TaskWhereUniqueInput | Prisma.TaskWhereUniqueInput[] + update?: Prisma.TaskUpdateWithWhereUniqueWithoutParentInput | Prisma.TaskUpdateWithWhereUniqueWithoutParentInput[] + updateMany?: Prisma.TaskUpdateManyWithWhereWithoutParentInput | Prisma.TaskUpdateManyWithWhereWithoutParentInput[] + deleteMany?: Prisma.TaskScalarWhereInput | Prisma.TaskScalarWhereInput[] +} + +export type TaskCreateNestedOneWithoutMembersInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutMembersInput + connect?: Prisma.TaskWhereUniqueInput +} + +export type TaskUpdateOneRequiredWithoutMembersNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutMembersInput + upsert?: Prisma.TaskUpsertWithoutMembersInput + connect?: Prisma.TaskWhereUniqueInput + update?: Prisma.XOR, Prisma.TaskUncheckedUpdateWithoutMembersInput> +} + +export type TaskCreateNestedOneWithoutTagsInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutTagsInput + connect?: Prisma.TaskWhereUniqueInput +} + +export type TaskUpdateOneRequiredWithoutTagsNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.TaskCreateOrConnectWithoutTagsInput + upsert?: Prisma.TaskUpsertWithoutTagsInput + connect?: Prisma.TaskWhereUniqueInput + update?: Prisma.XOR, Prisma.TaskUncheckedUpdateWithoutTagsInput> +} + +export type TaskCreateWithoutAssigneeInput = { + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null + project: Prisma.ProjectCreateNestedOneWithoutTasksInput + column: Prisma.BoardColumnCreateNestedOneWithoutTasksInput + parent?: Prisma.TaskCreateNestedOneWithoutChildrenInput + children?: Prisma.TaskCreateNestedManyWithoutParentInput + creator: Prisma.UserCreateNestedOneWithoutCreatedTasksInput + members?: Prisma.TaskMemberCreateNestedManyWithoutTaskInput + tags?: Prisma.TaskTagCreateNestedManyWithoutTaskInput +} + +export type TaskUncheckedCreateWithoutAssigneeInput = { + id?: number + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + projectId: number + columnId: number + parentId?: number | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + creatorId: number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null + children?: Prisma.TaskUncheckedCreateNestedManyWithoutParentInput + members?: Prisma.TaskMemberUncheckedCreateNestedManyWithoutTaskInput + tags?: Prisma.TaskTagUncheckedCreateNestedManyWithoutTaskInput +} + +export type TaskCreateOrConnectWithoutAssigneeInput = { + where: Prisma.TaskWhereUniqueInput + create: Prisma.XOR +} + +export type TaskCreateManyAssigneeInputEnvelope = { + data: Prisma.TaskCreateManyAssigneeInput | Prisma.TaskCreateManyAssigneeInput[] + skipDuplicates?: boolean +} + +export type TaskCreateWithoutCreatorInput = { + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null + project: Prisma.ProjectCreateNestedOneWithoutTasksInput + column: Prisma.BoardColumnCreateNestedOneWithoutTasksInput + parent?: Prisma.TaskCreateNestedOneWithoutChildrenInput + children?: Prisma.TaskCreateNestedManyWithoutParentInput + assignee?: Prisma.UserCreateNestedOneWithoutAssignedTasksInput + members?: Prisma.TaskMemberCreateNestedManyWithoutTaskInput + tags?: Prisma.TaskTagCreateNestedManyWithoutTaskInput +} + +export type TaskUncheckedCreateWithoutCreatorInput = { + id?: number + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + projectId: number + columnId: number + parentId?: number | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + assigneeId?: number | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null + children?: Prisma.TaskUncheckedCreateNestedManyWithoutParentInput + members?: Prisma.TaskMemberUncheckedCreateNestedManyWithoutTaskInput + tags?: Prisma.TaskTagUncheckedCreateNestedManyWithoutTaskInput +} + +export type TaskCreateOrConnectWithoutCreatorInput = { + where: Prisma.TaskWhereUniqueInput + create: Prisma.XOR +} + +export type TaskCreateManyCreatorInputEnvelope = { + data: Prisma.TaskCreateManyCreatorInput | Prisma.TaskCreateManyCreatorInput[] + skipDuplicates?: boolean +} + +export type TaskUpsertWithWhereUniqueWithoutAssigneeInput = { + where: Prisma.TaskWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type TaskUpdateWithWhereUniqueWithoutAssigneeInput = { + where: Prisma.TaskWhereUniqueInput + data: Prisma.XOR +} + +export type TaskUpdateManyWithWhereWithoutAssigneeInput = { + where: Prisma.TaskScalarWhereInput + data: Prisma.XOR +} + +export type TaskScalarWhereInput = { + AND?: Prisma.TaskScalarWhereInput | Prisma.TaskScalarWhereInput[] + OR?: Prisma.TaskScalarWhereInput[] + NOT?: Prisma.TaskScalarWhereInput | Prisma.TaskScalarWhereInput[] + id?: Prisma.IntFilter<"Task"> | number + uuid?: Prisma.StringFilter<"Task"> | string + projectUuid?: Prisma.StringNullableFilter<"Task"> | string | null + columnUuid?: Prisma.StringNullableFilter<"Task"> | string | null + parentUuid?: Prisma.StringNullableFilter<"Task"> | string | null + projectId?: Prisma.IntFilter<"Task"> | number + columnId?: Prisma.IntFilter<"Task"> | number + parentId?: Prisma.IntNullableFilter<"Task"> | number | null + title?: Prisma.StringFilter<"Task"> | string + description?: Prisma.StringFilter<"Task"> | string + kind?: Prisma.StringNullableFilter<"Task"> | string | null + markDate?: Prisma.DateTimeNullableFilter<"Task"> | Date | string | null + status?: Prisma.StringFilter<"Task"> | string + doneAt?: Prisma.DateTimeNullableFilter<"Task"> | Date | string | null + assigneeId?: Prisma.IntNullableFilter<"Task"> | number | null + creatorId?: Prisma.IntFilter<"Task"> | number + extras?: Prisma.JsonFilter<"Task"> + sortOrder?: Prisma.IntFilter<"Task"> | number + archived?: Prisma.BoolFilter<"Task"> | boolean + archivedAt?: Prisma.DateTimeNullableFilter<"Task"> | Date | string | null + archiveSource?: Prisma.StringNullableFilter<"Task"> | string | null + createdAt?: Prisma.DateTimeFilter<"Task"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"Task"> | Date | string + number?: Prisma.IntNullableFilter<"Task"> | number | null +} + +export type TaskUpsertWithWhereUniqueWithoutCreatorInput = { + where: Prisma.TaskWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type TaskUpdateWithWhereUniqueWithoutCreatorInput = { + where: Prisma.TaskWhereUniqueInput + data: Prisma.XOR +} + +export type TaskUpdateManyWithWhereWithoutCreatorInput = { + where: Prisma.TaskScalarWhereInput + data: Prisma.XOR +} + +export type TaskCreateWithoutProjectInput = { + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null + column: Prisma.BoardColumnCreateNestedOneWithoutTasksInput + parent?: Prisma.TaskCreateNestedOneWithoutChildrenInput + children?: Prisma.TaskCreateNestedManyWithoutParentInput + assignee?: Prisma.UserCreateNestedOneWithoutAssignedTasksInput + creator: Prisma.UserCreateNestedOneWithoutCreatedTasksInput + members?: Prisma.TaskMemberCreateNestedManyWithoutTaskInput + tags?: Prisma.TaskTagCreateNestedManyWithoutTaskInput +} + +export type TaskUncheckedCreateWithoutProjectInput = { + id?: number + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + columnId: number + parentId?: number | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + assigneeId?: number | null + creatorId: number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null + children?: Prisma.TaskUncheckedCreateNestedManyWithoutParentInput + members?: Prisma.TaskMemberUncheckedCreateNestedManyWithoutTaskInput + tags?: Prisma.TaskTagUncheckedCreateNestedManyWithoutTaskInput +} + +export type TaskCreateOrConnectWithoutProjectInput = { + where: Prisma.TaskWhereUniqueInput + create: Prisma.XOR +} + +export type TaskCreateManyProjectInputEnvelope = { + data: Prisma.TaskCreateManyProjectInput | Prisma.TaskCreateManyProjectInput[] + skipDuplicates?: boolean +} + +export type TaskUpsertWithWhereUniqueWithoutProjectInput = { + where: Prisma.TaskWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type TaskUpdateWithWhereUniqueWithoutProjectInput = { + where: Prisma.TaskWhereUniqueInput + data: Prisma.XOR +} + +export type TaskUpdateManyWithWhereWithoutProjectInput = { + where: Prisma.TaskScalarWhereInput + data: Prisma.XOR +} + +export type TaskCreateWithoutColumnInput = { + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null + project: Prisma.ProjectCreateNestedOneWithoutTasksInput + parent?: Prisma.TaskCreateNestedOneWithoutChildrenInput + children?: Prisma.TaskCreateNestedManyWithoutParentInput + assignee?: Prisma.UserCreateNestedOneWithoutAssignedTasksInput + creator: Prisma.UserCreateNestedOneWithoutCreatedTasksInput + members?: Prisma.TaskMemberCreateNestedManyWithoutTaskInput + tags?: Prisma.TaskTagCreateNestedManyWithoutTaskInput +} + +export type TaskUncheckedCreateWithoutColumnInput = { + id?: number + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + projectId: number + parentId?: number | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + assigneeId?: number | null + creatorId: number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null + children?: Prisma.TaskUncheckedCreateNestedManyWithoutParentInput + members?: Prisma.TaskMemberUncheckedCreateNestedManyWithoutTaskInput + tags?: Prisma.TaskTagUncheckedCreateNestedManyWithoutTaskInput +} + +export type TaskCreateOrConnectWithoutColumnInput = { + where: Prisma.TaskWhereUniqueInput + create: Prisma.XOR +} + +export type TaskCreateManyColumnInputEnvelope = { + data: Prisma.TaskCreateManyColumnInput | Prisma.TaskCreateManyColumnInput[] + skipDuplicates?: boolean +} + +export type TaskUpsertWithWhereUniqueWithoutColumnInput = { + where: Prisma.TaskWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type TaskUpdateWithWhereUniqueWithoutColumnInput = { + where: Prisma.TaskWhereUniqueInput + data: Prisma.XOR +} + +export type TaskUpdateManyWithWhereWithoutColumnInput = { + where: Prisma.TaskScalarWhereInput + data: Prisma.XOR +} + +export type TaskCreateWithoutChildrenInput = { + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null + project: Prisma.ProjectCreateNestedOneWithoutTasksInput + column: Prisma.BoardColumnCreateNestedOneWithoutTasksInput + parent?: Prisma.TaskCreateNestedOneWithoutChildrenInput + assignee?: Prisma.UserCreateNestedOneWithoutAssignedTasksInput + creator: Prisma.UserCreateNestedOneWithoutCreatedTasksInput + members?: Prisma.TaskMemberCreateNestedManyWithoutTaskInput + tags?: Prisma.TaskTagCreateNestedManyWithoutTaskInput +} + +export type TaskUncheckedCreateWithoutChildrenInput = { + id?: number + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + projectId: number + columnId: number + parentId?: number | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + assigneeId?: number | null + creatorId: number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null + members?: Prisma.TaskMemberUncheckedCreateNestedManyWithoutTaskInput + tags?: Prisma.TaskTagUncheckedCreateNestedManyWithoutTaskInput +} + +export type TaskCreateOrConnectWithoutChildrenInput = { + where: Prisma.TaskWhereUniqueInput + create: Prisma.XOR +} + +export type TaskCreateWithoutParentInput = { + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null + project: Prisma.ProjectCreateNestedOneWithoutTasksInput + column: Prisma.BoardColumnCreateNestedOneWithoutTasksInput + children?: Prisma.TaskCreateNestedManyWithoutParentInput + assignee?: Prisma.UserCreateNestedOneWithoutAssignedTasksInput + creator: Prisma.UserCreateNestedOneWithoutCreatedTasksInput + members?: Prisma.TaskMemberCreateNestedManyWithoutTaskInput + tags?: Prisma.TaskTagCreateNestedManyWithoutTaskInput +} + +export type TaskUncheckedCreateWithoutParentInput = { + id?: number + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + projectId: number + columnId: number + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + assigneeId?: number | null + creatorId: number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null + children?: Prisma.TaskUncheckedCreateNestedManyWithoutParentInput + members?: Prisma.TaskMemberUncheckedCreateNestedManyWithoutTaskInput + tags?: Prisma.TaskTagUncheckedCreateNestedManyWithoutTaskInput +} + +export type TaskCreateOrConnectWithoutParentInput = { + where: Prisma.TaskWhereUniqueInput + create: Prisma.XOR +} + +export type TaskCreateManyParentInputEnvelope = { + data: Prisma.TaskCreateManyParentInput | Prisma.TaskCreateManyParentInput[] + skipDuplicates?: boolean +} + +export type TaskUpsertWithoutChildrenInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.TaskWhereInput +} + +export type TaskUpdateToOneWithWhereWithoutChildrenInput = { + where?: Prisma.TaskWhereInput + data: Prisma.XOR +} + +export type TaskUpdateWithoutChildrenInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + project?: Prisma.ProjectUpdateOneRequiredWithoutTasksNestedInput + column?: Prisma.BoardColumnUpdateOneRequiredWithoutTasksNestedInput + parent?: Prisma.TaskUpdateOneWithoutChildrenNestedInput + assignee?: Prisma.UserUpdateOneWithoutAssignedTasksNestedInput + creator?: Prisma.UserUpdateOneRequiredWithoutCreatedTasksNestedInput + members?: Prisma.TaskMemberUpdateManyWithoutTaskNestedInput + tags?: Prisma.TaskTagUpdateManyWithoutTaskNestedInput +} + +export type TaskUncheckedUpdateWithoutChildrenInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + columnId?: Prisma.IntFieldUpdateOperationsInput | number + parentId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + assigneeId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + creatorId?: Prisma.IntFieldUpdateOperationsInput | number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + members?: Prisma.TaskMemberUncheckedUpdateManyWithoutTaskNestedInput + tags?: Prisma.TaskTagUncheckedUpdateManyWithoutTaskNestedInput +} + +export type TaskUpsertWithWhereUniqueWithoutParentInput = { + where: Prisma.TaskWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type TaskUpdateWithWhereUniqueWithoutParentInput = { + where: Prisma.TaskWhereUniqueInput + data: Prisma.XOR +} + +export type TaskUpdateManyWithWhereWithoutParentInput = { + where: Prisma.TaskScalarWhereInput + data: Prisma.XOR +} + +export type TaskCreateWithoutMembersInput = { + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null + project: Prisma.ProjectCreateNestedOneWithoutTasksInput + column: Prisma.BoardColumnCreateNestedOneWithoutTasksInput + parent?: Prisma.TaskCreateNestedOneWithoutChildrenInput + children?: Prisma.TaskCreateNestedManyWithoutParentInput + assignee?: Prisma.UserCreateNestedOneWithoutAssignedTasksInput + creator: Prisma.UserCreateNestedOneWithoutCreatedTasksInput + tags?: Prisma.TaskTagCreateNestedManyWithoutTaskInput +} + +export type TaskUncheckedCreateWithoutMembersInput = { + id?: number + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + projectId: number + columnId: number + parentId?: number | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + assigneeId?: number | null + creatorId: number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null + children?: Prisma.TaskUncheckedCreateNestedManyWithoutParentInput + tags?: Prisma.TaskTagUncheckedCreateNestedManyWithoutTaskInput +} + +export type TaskCreateOrConnectWithoutMembersInput = { + where: Prisma.TaskWhereUniqueInput + create: Prisma.XOR +} + +export type TaskUpsertWithoutMembersInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.TaskWhereInput +} + +export type TaskUpdateToOneWithWhereWithoutMembersInput = { + where?: Prisma.TaskWhereInput + data: Prisma.XOR +} + +export type TaskUpdateWithoutMembersInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + project?: Prisma.ProjectUpdateOneRequiredWithoutTasksNestedInput + column?: Prisma.BoardColumnUpdateOneRequiredWithoutTasksNestedInput + parent?: Prisma.TaskUpdateOneWithoutChildrenNestedInput + children?: Prisma.TaskUpdateManyWithoutParentNestedInput + assignee?: Prisma.UserUpdateOneWithoutAssignedTasksNestedInput + creator?: Prisma.UserUpdateOneRequiredWithoutCreatedTasksNestedInput + tags?: Prisma.TaskTagUpdateManyWithoutTaskNestedInput +} + +export type TaskUncheckedUpdateWithoutMembersInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + columnId?: Prisma.IntFieldUpdateOperationsInput | number + parentId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + assigneeId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + creatorId?: Prisma.IntFieldUpdateOperationsInput | number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + children?: Prisma.TaskUncheckedUpdateManyWithoutParentNestedInput + tags?: Prisma.TaskTagUncheckedUpdateManyWithoutTaskNestedInput +} + +export type TaskCreateWithoutTagsInput = { + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null + project: Prisma.ProjectCreateNestedOneWithoutTasksInput + column: Prisma.BoardColumnCreateNestedOneWithoutTasksInput + parent?: Prisma.TaskCreateNestedOneWithoutChildrenInput + children?: Prisma.TaskCreateNestedManyWithoutParentInput + assignee?: Prisma.UserCreateNestedOneWithoutAssignedTasksInput + creator: Prisma.UserCreateNestedOneWithoutCreatedTasksInput + members?: Prisma.TaskMemberCreateNestedManyWithoutTaskInput +} + +export type TaskUncheckedCreateWithoutTagsInput = { + id?: number + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + projectId: number + columnId: number + parentId?: number | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + assigneeId?: number | null + creatorId: number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null + children?: Prisma.TaskUncheckedCreateNestedManyWithoutParentInput + members?: Prisma.TaskMemberUncheckedCreateNestedManyWithoutTaskInput +} + +export type TaskCreateOrConnectWithoutTagsInput = { + where: Prisma.TaskWhereUniqueInput + create: Prisma.XOR +} + +export type TaskUpsertWithoutTagsInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.TaskWhereInput +} + +export type TaskUpdateToOneWithWhereWithoutTagsInput = { + where?: Prisma.TaskWhereInput + data: Prisma.XOR +} + +export type TaskUpdateWithoutTagsInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + project?: Prisma.ProjectUpdateOneRequiredWithoutTasksNestedInput + column?: Prisma.BoardColumnUpdateOneRequiredWithoutTasksNestedInput + parent?: Prisma.TaskUpdateOneWithoutChildrenNestedInput + children?: Prisma.TaskUpdateManyWithoutParentNestedInput + assignee?: Prisma.UserUpdateOneWithoutAssignedTasksNestedInput + creator?: Prisma.UserUpdateOneRequiredWithoutCreatedTasksNestedInput + members?: Prisma.TaskMemberUpdateManyWithoutTaskNestedInput +} + +export type TaskUncheckedUpdateWithoutTagsInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + columnId?: Prisma.IntFieldUpdateOperationsInput | number + parentId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + assigneeId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + creatorId?: Prisma.IntFieldUpdateOperationsInput | number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + children?: Prisma.TaskUncheckedUpdateManyWithoutParentNestedInput + members?: Prisma.TaskMemberUncheckedUpdateManyWithoutTaskNestedInput +} + +export type TaskCreateManyAssigneeInput = { + id?: number + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + projectId: number + columnId: number + parentId?: number | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + creatorId: number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null +} + +export type TaskCreateManyCreatorInput = { + id?: number + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + projectId: number + columnId: number + parentId?: number | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + assigneeId?: number | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null +} + +export type TaskUpdateWithoutAssigneeInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + project?: Prisma.ProjectUpdateOneRequiredWithoutTasksNestedInput + column?: Prisma.BoardColumnUpdateOneRequiredWithoutTasksNestedInput + parent?: Prisma.TaskUpdateOneWithoutChildrenNestedInput + children?: Prisma.TaskUpdateManyWithoutParentNestedInput + creator?: Prisma.UserUpdateOneRequiredWithoutCreatedTasksNestedInput + members?: Prisma.TaskMemberUpdateManyWithoutTaskNestedInput + tags?: Prisma.TaskTagUpdateManyWithoutTaskNestedInput +} + +export type TaskUncheckedUpdateWithoutAssigneeInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + columnId?: Prisma.IntFieldUpdateOperationsInput | number + parentId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + creatorId?: Prisma.IntFieldUpdateOperationsInput | number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + children?: Prisma.TaskUncheckedUpdateManyWithoutParentNestedInput + members?: Prisma.TaskMemberUncheckedUpdateManyWithoutTaskNestedInput + tags?: Prisma.TaskTagUncheckedUpdateManyWithoutTaskNestedInput +} + +export type TaskUncheckedUpdateManyWithoutAssigneeInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + columnId?: Prisma.IntFieldUpdateOperationsInput | number + parentId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + creatorId?: Prisma.IntFieldUpdateOperationsInput | number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null +} + +export type TaskUpdateWithoutCreatorInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + project?: Prisma.ProjectUpdateOneRequiredWithoutTasksNestedInput + column?: Prisma.BoardColumnUpdateOneRequiredWithoutTasksNestedInput + parent?: Prisma.TaskUpdateOneWithoutChildrenNestedInput + children?: Prisma.TaskUpdateManyWithoutParentNestedInput + assignee?: Prisma.UserUpdateOneWithoutAssignedTasksNestedInput + members?: Prisma.TaskMemberUpdateManyWithoutTaskNestedInput + tags?: Prisma.TaskTagUpdateManyWithoutTaskNestedInput +} + +export type TaskUncheckedUpdateWithoutCreatorInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + columnId?: Prisma.IntFieldUpdateOperationsInput | number + parentId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + assigneeId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + children?: Prisma.TaskUncheckedUpdateManyWithoutParentNestedInput + members?: Prisma.TaskMemberUncheckedUpdateManyWithoutTaskNestedInput + tags?: Prisma.TaskTagUncheckedUpdateManyWithoutTaskNestedInput +} + +export type TaskUncheckedUpdateManyWithoutCreatorInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + columnId?: Prisma.IntFieldUpdateOperationsInput | number + parentId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + assigneeId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null +} + +export type TaskCreateManyProjectInput = { + id?: number + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + columnId: number + parentId?: number | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + assigneeId?: number | null + creatorId: number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null +} + +export type TaskUpdateWithoutProjectInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + column?: Prisma.BoardColumnUpdateOneRequiredWithoutTasksNestedInput + parent?: Prisma.TaskUpdateOneWithoutChildrenNestedInput + children?: Prisma.TaskUpdateManyWithoutParentNestedInput + assignee?: Prisma.UserUpdateOneWithoutAssignedTasksNestedInput + creator?: Prisma.UserUpdateOneRequiredWithoutCreatedTasksNestedInput + members?: Prisma.TaskMemberUpdateManyWithoutTaskNestedInput + tags?: Prisma.TaskTagUpdateManyWithoutTaskNestedInput +} + +export type TaskUncheckedUpdateWithoutProjectInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnId?: Prisma.IntFieldUpdateOperationsInput | number + parentId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + assigneeId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + creatorId?: Prisma.IntFieldUpdateOperationsInput | number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + children?: Prisma.TaskUncheckedUpdateManyWithoutParentNestedInput + members?: Prisma.TaskMemberUncheckedUpdateManyWithoutTaskNestedInput + tags?: Prisma.TaskTagUncheckedUpdateManyWithoutTaskNestedInput +} + +export type TaskUncheckedUpdateManyWithoutProjectInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnId?: Prisma.IntFieldUpdateOperationsInput | number + parentId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + assigneeId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + creatorId?: Prisma.IntFieldUpdateOperationsInput | number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null +} + +export type TaskCreateManyColumnInput = { + id?: number + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + projectId: number + parentId?: number | null + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + assigneeId?: number | null + creatorId: number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null +} + +export type TaskUpdateWithoutColumnInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + project?: Prisma.ProjectUpdateOneRequiredWithoutTasksNestedInput + parent?: Prisma.TaskUpdateOneWithoutChildrenNestedInput + children?: Prisma.TaskUpdateManyWithoutParentNestedInput + assignee?: Prisma.UserUpdateOneWithoutAssignedTasksNestedInput + creator?: Prisma.UserUpdateOneRequiredWithoutCreatedTasksNestedInput + members?: Prisma.TaskMemberUpdateManyWithoutTaskNestedInput + tags?: Prisma.TaskTagUpdateManyWithoutTaskNestedInput +} + +export type TaskUncheckedUpdateWithoutColumnInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + parentId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + assigneeId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + creatorId?: Prisma.IntFieldUpdateOperationsInput | number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + children?: Prisma.TaskUncheckedUpdateManyWithoutParentNestedInput + members?: Prisma.TaskMemberUncheckedUpdateManyWithoutTaskNestedInput + tags?: Prisma.TaskTagUncheckedUpdateManyWithoutTaskNestedInput +} + +export type TaskUncheckedUpdateManyWithoutColumnInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + parentId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + assigneeId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + creatorId?: Prisma.IntFieldUpdateOperationsInput | number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null +} + +export type TaskCreateManyParentInput = { + id?: number + uuid: string + projectUuid?: string | null + columnUuid?: string | null + parentUuid?: string | null + projectId: number + columnId: number + title: string + description?: string + kind?: string | null + markDate?: Date | string | null + status?: string + doneAt?: Date | string | null + assigneeId?: number | null + creatorId: number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: number + archived?: boolean + archivedAt?: Date | string | null + archiveSource?: string | null + createdAt?: Date | string + updatedAt?: Date | string + number?: number | null +} + +export type TaskUpdateWithoutParentInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + project?: Prisma.ProjectUpdateOneRequiredWithoutTasksNestedInput + column?: Prisma.BoardColumnUpdateOneRequiredWithoutTasksNestedInput + children?: Prisma.TaskUpdateManyWithoutParentNestedInput + assignee?: Prisma.UserUpdateOneWithoutAssignedTasksNestedInput + creator?: Prisma.UserUpdateOneRequiredWithoutCreatedTasksNestedInput + members?: Prisma.TaskMemberUpdateManyWithoutTaskNestedInput + tags?: Prisma.TaskTagUpdateManyWithoutTaskNestedInput +} + +export type TaskUncheckedUpdateWithoutParentInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + columnId?: Prisma.IntFieldUpdateOperationsInput | number + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + assigneeId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + creatorId?: Prisma.IntFieldUpdateOperationsInput | number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + children?: Prisma.TaskUncheckedUpdateManyWithoutParentNestedInput + members?: Prisma.TaskMemberUncheckedUpdateManyWithoutTaskNestedInput + tags?: Prisma.TaskTagUncheckedUpdateManyWithoutTaskNestedInput +} + +export type TaskUncheckedUpdateManyWithoutParentInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + projectUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + columnUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + parentUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + projectId?: Prisma.IntFieldUpdateOperationsInput | number + columnId?: Prisma.IntFieldUpdateOperationsInput | number + title?: Prisma.StringFieldUpdateOperationsInput | string + description?: Prisma.StringFieldUpdateOperationsInput | string + kind?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + markDate?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + status?: Prisma.StringFieldUpdateOperationsInput | string + doneAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + assigneeId?: Prisma.NullableIntFieldUpdateOperationsInput | number | null + creatorId?: Prisma.IntFieldUpdateOperationsInput | number + extras?: Prisma.JsonNullValueInput | runtime.InputJsonValue + sortOrder?: Prisma.IntFieldUpdateOperationsInput | number + archived?: Prisma.BoolFieldUpdateOperationsInput | boolean + archivedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + archiveSource?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + number?: Prisma.NullableIntFieldUpdateOperationsInput | number | null +} + + +/** + * Count Type TaskCountOutputType + */ + +export type TaskCountOutputType = { + children: number + members: number + tags: number +} + +export type TaskCountOutputTypeSelect = { + children?: boolean | TaskCountOutputTypeCountChildrenArgs + members?: boolean | TaskCountOutputTypeCountMembersArgs + tags?: boolean | TaskCountOutputTypeCountTagsArgs +} + +/** + * TaskCountOutputType without action + */ +export type TaskCountOutputTypeDefaultArgs = { + /** + * Select specific fields to fetch from the TaskCountOutputType + */ + select?: Prisma.TaskCountOutputTypeSelect | null +} + +/** + * TaskCountOutputType without action + */ +export type TaskCountOutputTypeCountChildrenArgs = { + where?: Prisma.TaskWhereInput +} + +/** + * TaskCountOutputType without action + */ +export type TaskCountOutputTypeCountMembersArgs = { + where?: Prisma.TaskMemberWhereInput +} + +/** + * TaskCountOutputType without action + */ +export type TaskCountOutputTypeCountTagsArgs = { + where?: Prisma.TaskTagWhereInput +} + + +export type TaskSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + projectUuid?: boolean + columnUuid?: boolean + parentUuid?: boolean + projectId?: boolean + columnId?: boolean + parentId?: boolean + title?: boolean + description?: boolean + kind?: boolean + markDate?: boolean + status?: boolean + doneAt?: boolean + assigneeId?: boolean + creatorId?: boolean + extras?: boolean + sortOrder?: boolean + archived?: boolean + archivedAt?: boolean + archiveSource?: boolean + createdAt?: boolean + updatedAt?: boolean + number?: boolean + project?: boolean | Prisma.ProjectDefaultArgs + column?: boolean | Prisma.BoardColumnDefaultArgs + parent?: boolean | Prisma.Task$parentArgs + children?: boolean | Prisma.Task$childrenArgs + assignee?: boolean | Prisma.Task$assigneeArgs + creator?: boolean | Prisma.UserDefaultArgs + members?: boolean | Prisma.Task$membersArgs + tags?: boolean | Prisma.Task$tagsArgs + _count?: boolean | Prisma.TaskCountOutputTypeDefaultArgs +}, ExtArgs["result"]["task"]> + +export type TaskSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + projectUuid?: boolean + columnUuid?: boolean + parentUuid?: boolean + projectId?: boolean + columnId?: boolean + parentId?: boolean + title?: boolean + description?: boolean + kind?: boolean + markDate?: boolean + status?: boolean + doneAt?: boolean + assigneeId?: boolean + creatorId?: boolean + extras?: boolean + sortOrder?: boolean + archived?: boolean + archivedAt?: boolean + archiveSource?: boolean + createdAt?: boolean + updatedAt?: boolean + number?: boolean + project?: boolean | Prisma.ProjectDefaultArgs + column?: boolean | Prisma.BoardColumnDefaultArgs + parent?: boolean | Prisma.Task$parentArgs + assignee?: boolean | Prisma.Task$assigneeArgs + creator?: boolean | Prisma.UserDefaultArgs +}, ExtArgs["result"]["task"]> + +export type TaskSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + projectUuid?: boolean + columnUuid?: boolean + parentUuid?: boolean + projectId?: boolean + columnId?: boolean + parentId?: boolean + title?: boolean + description?: boolean + kind?: boolean + markDate?: boolean + status?: boolean + doneAt?: boolean + assigneeId?: boolean + creatorId?: boolean + extras?: boolean + sortOrder?: boolean + archived?: boolean + archivedAt?: boolean + archiveSource?: boolean + createdAt?: boolean + updatedAt?: boolean + number?: boolean + project?: boolean | Prisma.ProjectDefaultArgs + column?: boolean | Prisma.BoardColumnDefaultArgs + parent?: boolean | Prisma.Task$parentArgs + assignee?: boolean | Prisma.Task$assigneeArgs + creator?: boolean | Prisma.UserDefaultArgs +}, ExtArgs["result"]["task"]> + +export type TaskSelectScalar = { + id?: boolean + uuid?: boolean + projectUuid?: boolean + columnUuid?: boolean + parentUuid?: boolean + projectId?: boolean + columnId?: boolean + parentId?: boolean + title?: boolean + description?: boolean + kind?: boolean + markDate?: boolean + status?: boolean + doneAt?: boolean + assigneeId?: boolean + creatorId?: boolean + extras?: boolean + sortOrder?: boolean + archived?: boolean + archivedAt?: boolean + archiveSource?: boolean + createdAt?: boolean + updatedAt?: boolean + number?: boolean +} + +export type TaskOmit = runtime.Types.Extensions.GetOmit<"id" | "uuid" | "projectUuid" | "columnUuid" | "parentUuid" | "projectId" | "columnId" | "parentId" | "title" | "description" | "kind" | "markDate" | "status" | "doneAt" | "assigneeId" | "creatorId" | "extras" | "sortOrder" | "archived" | "archivedAt" | "archiveSource" | "createdAt" | "updatedAt" | "number", ExtArgs["result"]["task"]> +export type TaskInclude = { + project?: boolean | Prisma.ProjectDefaultArgs + column?: boolean | Prisma.BoardColumnDefaultArgs + parent?: boolean | Prisma.Task$parentArgs + children?: boolean | Prisma.Task$childrenArgs + assignee?: boolean | Prisma.Task$assigneeArgs + creator?: boolean | Prisma.UserDefaultArgs + members?: boolean | Prisma.Task$membersArgs + tags?: boolean | Prisma.Task$tagsArgs + _count?: boolean | Prisma.TaskCountOutputTypeDefaultArgs +} +export type TaskIncludeCreateManyAndReturn = { + project?: boolean | Prisma.ProjectDefaultArgs + column?: boolean | Prisma.BoardColumnDefaultArgs + parent?: boolean | Prisma.Task$parentArgs + assignee?: boolean | Prisma.Task$assigneeArgs + creator?: boolean | Prisma.UserDefaultArgs +} +export type TaskIncludeUpdateManyAndReturn = { + project?: boolean | Prisma.ProjectDefaultArgs + column?: boolean | Prisma.BoardColumnDefaultArgs + parent?: boolean | Prisma.Task$parentArgs + assignee?: boolean | Prisma.Task$assigneeArgs + creator?: boolean | Prisma.UserDefaultArgs +} + +export type $TaskPayload = { + name: "Task" + objects: { + project: Prisma.$ProjectPayload + column: Prisma.$BoardColumnPayload + parent: Prisma.$TaskPayload | null + children: Prisma.$TaskPayload[] + assignee: Prisma.$UserPayload | null + creator: Prisma.$UserPayload + members: Prisma.$TaskMemberPayload[] + tags: Prisma.$TaskTagPayload[] + } + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: number + uuid: string + projectUuid: string | null + columnUuid: string | null + parentUuid: string | null + projectId: number + columnId: number + parentId: number | null + title: string + description: string + kind: string | null + markDate: Date | null + status: string + doneAt: Date | null + assigneeId: number | null + creatorId: number + extras: runtime.JsonValue + sortOrder: number + archived: boolean + archivedAt: Date | null + archiveSource: string | null + createdAt: Date + updatedAt: Date + number: number | null + }, ExtArgs["result"]["task"]> + composites: {} +} + +export type TaskGetPayload = runtime.Types.Result.GetResult + +export type TaskCountArgs = + Omit & { + select?: TaskCountAggregateInputType | true + } + +export interface TaskDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['Task'], meta: { name: 'Task' } } + /** + * Find zero or one Task that matches the filter. + * @param {TaskFindUniqueArgs} args - Arguments to find a Task + * @example + * // Get one Task + * const task = await prisma.task.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__TaskClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one Task that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {TaskFindUniqueOrThrowArgs} args - Arguments to find a Task + * @example + * // Get one Task + * const task = await prisma.task.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__TaskClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first Task that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskFindFirstArgs} args - Arguments to find a Task + * @example + * // Get one Task + * const task = await prisma.task.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__TaskClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first Task that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskFindFirstOrThrowArgs} args - Arguments to find a Task + * @example + * // Get one Task + * const task = await prisma.task.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__TaskClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more Tasks that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all Tasks + * const tasks = await prisma.task.findMany() + * + * // Get first 10 Tasks + * const tasks = await prisma.task.findMany({ take: 10 }) + * + * // Only select the `id` + * const taskWithIdOnly = await prisma.task.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a Task. + * @param {TaskCreateArgs} args - Arguments to create a Task. + * @example + * // Create one Task + * const Task = await prisma.task.create({ + * data: { + * // ... data to create a Task + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__TaskClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many Tasks. + * @param {TaskCreateManyArgs} args - Arguments to create many Tasks. + * @example + * // Create many Tasks + * const task = await prisma.task.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many Tasks and returns the data saved in the database. + * @param {TaskCreateManyAndReturnArgs} args - Arguments to create many Tasks. + * @example + * // Create many Tasks + * const task = await prisma.task.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many Tasks and only return the `id` + * const taskWithIdOnly = await prisma.task.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a Task. + * @param {TaskDeleteArgs} args - Arguments to delete one Task. + * @example + * // Delete one Task + * const Task = await prisma.task.delete({ + * where: { + * // ... filter to delete one Task + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__TaskClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one Task. + * @param {TaskUpdateArgs} args - Arguments to update one Task. + * @example + * // Update one Task + * const task = await prisma.task.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__TaskClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more Tasks. + * @param {TaskDeleteManyArgs} args - Arguments to filter Tasks to delete. + * @example + * // Delete a few Tasks + * const { count } = await prisma.task.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more Tasks. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many Tasks + * const task = await prisma.task.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more Tasks and returns the data updated in the database. + * @param {TaskUpdateManyAndReturnArgs} args - Arguments to update many Tasks. + * @example + * // Update many Tasks + * const task = await prisma.task.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more Tasks and only return the `id` + * const taskWithIdOnly = await prisma.task.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one Task. + * @param {TaskUpsertArgs} args - Arguments to update or create a Task. + * @example + * // Update or create a Task + * const task = await prisma.task.upsert({ + * create: { + * // ... data to create a Task + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the Task we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__TaskClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of Tasks. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskCountArgs} args - Arguments to filter Tasks to count. + * @example + * // Count the number of Tasks + * const count = await prisma.task.count({ + * where: { + * // ... the filter for the Tasks we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a Task. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by Task. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends TaskGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: TaskGroupByArgs['orderBy'] } + : { orderBy?: TaskGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetTaskGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the Task model + */ +readonly fields: TaskFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for Task. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__TaskClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + project = {}>(args?: Prisma.Subset>): Prisma.Prisma__ProjectClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + column = {}>(args?: Prisma.Subset>): Prisma.Prisma__BoardColumnClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + parent = {}>(args?: Prisma.Subset>): Prisma.Prisma__TaskClient, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + children = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + assignee = {}>(args?: Prisma.Subset>): Prisma.Prisma__UserClient, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + creator = {}>(args?: Prisma.Subset>): Prisma.Prisma__UserClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + members = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + tags = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the Task model + */ +export interface TaskFieldRefs { + readonly id: Prisma.FieldRef<"Task", 'Int'> + readonly uuid: Prisma.FieldRef<"Task", 'String'> + readonly projectUuid: Prisma.FieldRef<"Task", 'String'> + readonly columnUuid: Prisma.FieldRef<"Task", 'String'> + readonly parentUuid: Prisma.FieldRef<"Task", 'String'> + readonly projectId: Prisma.FieldRef<"Task", 'Int'> + readonly columnId: Prisma.FieldRef<"Task", 'Int'> + readonly parentId: Prisma.FieldRef<"Task", 'Int'> + readonly title: Prisma.FieldRef<"Task", 'String'> + readonly description: Prisma.FieldRef<"Task", 'String'> + readonly kind: Prisma.FieldRef<"Task", 'String'> + readonly markDate: Prisma.FieldRef<"Task", 'DateTime'> + readonly status: Prisma.FieldRef<"Task", 'String'> + readonly doneAt: Prisma.FieldRef<"Task", 'DateTime'> + readonly assigneeId: Prisma.FieldRef<"Task", 'Int'> + readonly creatorId: Prisma.FieldRef<"Task", 'Int'> + readonly extras: Prisma.FieldRef<"Task", 'Json'> + readonly sortOrder: Prisma.FieldRef<"Task", 'Int'> + readonly archived: Prisma.FieldRef<"Task", 'Boolean'> + readonly archivedAt: Prisma.FieldRef<"Task", 'DateTime'> + readonly archiveSource: Prisma.FieldRef<"Task", 'String'> + readonly createdAt: Prisma.FieldRef<"Task", 'DateTime'> + readonly updatedAt: Prisma.FieldRef<"Task", 'DateTime'> + readonly number: Prisma.FieldRef<"Task", 'Int'> +} + + +// Custom InputTypes +/** + * Task findUnique + */ +export type TaskFindUniqueArgs = { + /** + * Select specific fields to fetch from the Task + */ + select?: Prisma.TaskSelect | null + /** + * Omit specific fields from the Task + */ + omit?: Prisma.TaskOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskInclude | null + /** + * Filter, which Task to fetch. + */ + where: Prisma.TaskWhereUniqueInput +} + +/** + * Task findUniqueOrThrow + */ +export type TaskFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the Task + */ + select?: Prisma.TaskSelect | null + /** + * Omit specific fields from the Task + */ + omit?: Prisma.TaskOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskInclude | null + /** + * Filter, which Task to fetch. + */ + where: Prisma.TaskWhereUniqueInput +} + +/** + * Task findFirst + */ +export type TaskFindFirstArgs = { + /** + * Select specific fields to fetch from the Task + */ + select?: Prisma.TaskSelect | null + /** + * Omit specific fields from the Task + */ + omit?: Prisma.TaskOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskInclude | null + /** + * Filter, which Task to fetch. + */ + where?: Prisma.TaskWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Tasks to fetch. + */ + orderBy?: Prisma.TaskOrderByWithRelationInput | Prisma.TaskOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for Tasks. + */ + cursor?: Prisma.TaskWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Tasks from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Tasks. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Tasks. + */ + distinct?: Prisma.TaskScalarFieldEnum | Prisma.TaskScalarFieldEnum[] +} + +/** + * Task findFirstOrThrow + */ +export type TaskFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the Task + */ + select?: Prisma.TaskSelect | null + /** + * Omit specific fields from the Task + */ + omit?: Prisma.TaskOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskInclude | null + /** + * Filter, which Task to fetch. + */ + where?: Prisma.TaskWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Tasks to fetch. + */ + orderBy?: Prisma.TaskOrderByWithRelationInput | Prisma.TaskOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for Tasks. + */ + cursor?: Prisma.TaskWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Tasks from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Tasks. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Tasks. + */ + distinct?: Prisma.TaskScalarFieldEnum | Prisma.TaskScalarFieldEnum[] +} + +/** + * Task findMany + */ +export type TaskFindManyArgs = { + /** + * Select specific fields to fetch from the Task + */ + select?: Prisma.TaskSelect | null + /** + * Omit specific fields from the Task + */ + omit?: Prisma.TaskOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskInclude | null + /** + * Filter, which Tasks to fetch. + */ + where?: Prisma.TaskWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Tasks to fetch. + */ + orderBy?: Prisma.TaskOrderByWithRelationInput | Prisma.TaskOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing Tasks. + */ + cursor?: Prisma.TaskWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Tasks from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Tasks. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Tasks. + */ + distinct?: Prisma.TaskScalarFieldEnum | Prisma.TaskScalarFieldEnum[] +} + +/** + * Task create + */ +export type TaskCreateArgs = { + /** + * Select specific fields to fetch from the Task + */ + select?: Prisma.TaskSelect | null + /** + * Omit specific fields from the Task + */ + omit?: Prisma.TaskOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskInclude | null + /** + * The data needed to create a Task. + */ + data: Prisma.XOR +} + +/** + * Task createMany + */ +export type TaskCreateManyArgs = { + /** + * The data used to create many Tasks. + */ + data: Prisma.TaskCreateManyInput | Prisma.TaskCreateManyInput[] + skipDuplicates?: boolean +} + +/** + * Task createManyAndReturn + */ +export type TaskCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the Task + */ + select?: Prisma.TaskSelectCreateManyAndReturn | null + /** + * Omit specific fields from the Task + */ + omit?: Prisma.TaskOmit | null + /** + * The data used to create many Tasks. + */ + data: Prisma.TaskCreateManyInput | Prisma.TaskCreateManyInput[] + skipDuplicates?: boolean + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskIncludeCreateManyAndReturn | null +} + +/** + * Task update + */ +export type TaskUpdateArgs = { + /** + * Select specific fields to fetch from the Task + */ + select?: Prisma.TaskSelect | null + /** + * Omit specific fields from the Task + */ + omit?: Prisma.TaskOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskInclude | null + /** + * The data needed to update a Task. + */ + data: Prisma.XOR + /** + * Choose, which Task to update. + */ + where: Prisma.TaskWhereUniqueInput +} + +/** + * Task updateMany + */ +export type TaskUpdateManyArgs = { + /** + * The data used to update Tasks. + */ + data: Prisma.XOR + /** + * Filter which Tasks to update + */ + where?: Prisma.TaskWhereInput + /** + * Limit how many Tasks to update. + */ + limit?: number +} + +/** + * Task updateManyAndReturn + */ +export type TaskUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the Task + */ + select?: Prisma.TaskSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the Task + */ + omit?: Prisma.TaskOmit | null + /** + * The data used to update Tasks. + */ + data: Prisma.XOR + /** + * Filter which Tasks to update + */ + where?: Prisma.TaskWhereInput + /** + * Limit how many Tasks to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskIncludeUpdateManyAndReturn | null +} + +/** + * Task upsert + */ +export type TaskUpsertArgs = { + /** + * Select specific fields to fetch from the Task + */ + select?: Prisma.TaskSelect | null + /** + * Omit specific fields from the Task + */ + omit?: Prisma.TaskOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskInclude | null + /** + * The filter to search for the Task to update in case it exists. + */ + where: Prisma.TaskWhereUniqueInput + /** + * In case the Task found by the `where` argument doesn't exist, create a new Task with this data. + */ + create: Prisma.XOR + /** + * In case the Task was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * Task delete + */ +export type TaskDeleteArgs = { + /** + * Select specific fields to fetch from the Task + */ + select?: Prisma.TaskSelect | null + /** + * Omit specific fields from the Task + */ + omit?: Prisma.TaskOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskInclude | null + /** + * Filter which Task to delete. + */ + where: Prisma.TaskWhereUniqueInput +} + +/** + * Task deleteMany + */ +export type TaskDeleteManyArgs = { + /** + * Filter which Tasks to delete + */ + where?: Prisma.TaskWhereInput + /** + * Limit how many Tasks to delete. + */ + limit?: number +} + +/** + * Task.parent + */ +export type Task$parentArgs = { + /** + * Select specific fields to fetch from the Task + */ + select?: Prisma.TaskSelect | null + /** + * Omit specific fields from the Task + */ + omit?: Prisma.TaskOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskInclude | null + where?: Prisma.TaskWhereInput +} + +/** + * Task.children + */ +export type Task$childrenArgs = { + /** + * Select specific fields to fetch from the Task + */ + select?: Prisma.TaskSelect | null + /** + * Omit specific fields from the Task + */ + omit?: Prisma.TaskOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskInclude | null + where?: Prisma.TaskWhereInput + orderBy?: Prisma.TaskOrderByWithRelationInput | Prisma.TaskOrderByWithRelationInput[] + cursor?: Prisma.TaskWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.TaskScalarFieldEnum | Prisma.TaskScalarFieldEnum[] +} + +/** + * Task.assignee + */ +export type Task$assigneeArgs = { + /** + * Select specific fields to fetch from the User + */ + select?: Prisma.UserSelect | null + /** + * Omit specific fields from the User + */ + omit?: Prisma.UserOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.UserInclude | null + where?: Prisma.UserWhereInput +} + +/** + * Task.members + */ +export type Task$membersArgs = { + /** + * Select specific fields to fetch from the TaskMember + */ + select?: Prisma.TaskMemberSelect | null + /** + * Omit specific fields from the TaskMember + */ + omit?: Prisma.TaskMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskMemberInclude | null + where?: Prisma.TaskMemberWhereInput + orderBy?: Prisma.TaskMemberOrderByWithRelationInput | Prisma.TaskMemberOrderByWithRelationInput[] + cursor?: Prisma.TaskMemberWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.TaskMemberScalarFieldEnum | Prisma.TaskMemberScalarFieldEnum[] +} + +/** + * Task.tags + */ +export type Task$tagsArgs = { + /** + * Select specific fields to fetch from the TaskTag + */ + select?: Prisma.TaskTagSelect | null + /** + * Omit specific fields from the TaskTag + */ + omit?: Prisma.TaskTagOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskTagInclude | null + where?: Prisma.TaskTagWhereInput + orderBy?: Prisma.TaskTagOrderByWithRelationInput | Prisma.TaskTagOrderByWithRelationInput[] + cursor?: Prisma.TaskTagWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.TaskTagScalarFieldEnum | Prisma.TaskTagScalarFieldEnum[] +} + +/** + * Task without action + */ +export type TaskDefaultArgs = { + /** + * Select specific fields to fetch from the Task + */ + select?: Prisma.TaskSelect | null + /** + * Omit specific fields from the Task + */ + omit?: Prisma.TaskOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskInclude | null +} diff --git a/generated/prisma/models/TaskMember.ts b/generated/prisma/models/TaskMember.ts new file mode 100644 index 0000000..84b8566 --- /dev/null +++ b/generated/prisma/models/TaskMember.ts @@ -0,0 +1,1520 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `TaskMember` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums.ts" +import type * as Prisma from "../internal/prismaNamespace.ts" + +/** + * Model TaskMember + * + */ +export type TaskMemberModel = runtime.Types.Result.DefaultSelection + +export type AggregateTaskMember = { + _count: TaskMemberCountAggregateOutputType | null + _avg: TaskMemberAvgAggregateOutputType | null + _sum: TaskMemberSumAggregateOutputType | null + _min: TaskMemberMinAggregateOutputType | null + _max: TaskMemberMaxAggregateOutputType | null +} + +export type TaskMemberAvgAggregateOutputType = { + id: number | null + taskId: number | null + userId: number | null +} + +export type TaskMemberSumAggregateOutputType = { + id: number | null + taskId: number | null + userId: number | null +} + +export type TaskMemberMinAggregateOutputType = { + id: number | null + uuid: string | null + taskUuid: string | null + taskId: number | null + userId: number | null +} + +export type TaskMemberMaxAggregateOutputType = { + id: number | null + uuid: string | null + taskUuid: string | null + taskId: number | null + userId: number | null +} + +export type TaskMemberCountAggregateOutputType = { + id: number + uuid: number + taskUuid: number + taskId: number + userId: number + _all: number +} + + +export type TaskMemberAvgAggregateInputType = { + id?: true + taskId?: true + userId?: true +} + +export type TaskMemberSumAggregateInputType = { + id?: true + taskId?: true + userId?: true +} + +export type TaskMemberMinAggregateInputType = { + id?: true + uuid?: true + taskUuid?: true + taskId?: true + userId?: true +} + +export type TaskMemberMaxAggregateInputType = { + id?: true + uuid?: true + taskUuid?: true + taskId?: true + userId?: true +} + +export type TaskMemberCountAggregateInputType = { + id?: true + uuid?: true + taskUuid?: true + taskId?: true + userId?: true + _all?: true +} + +export type TaskMemberAggregateArgs = { + /** + * Filter which TaskMember to aggregate. + */ + where?: Prisma.TaskMemberWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of TaskMembers to fetch. + */ + orderBy?: Prisma.TaskMemberOrderByWithRelationInput | Prisma.TaskMemberOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.TaskMemberWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` TaskMembers from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` TaskMembers. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned TaskMembers + **/ + _count?: true | TaskMemberCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to average + **/ + _avg?: TaskMemberAvgAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to sum + **/ + _sum?: TaskMemberSumAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: TaskMemberMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: TaskMemberMaxAggregateInputType +} + +export type GetTaskMemberAggregateType = { + [P in keyof T & keyof AggregateTaskMember]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type TaskMemberGroupByArgs = { + where?: Prisma.TaskMemberWhereInput + orderBy?: Prisma.TaskMemberOrderByWithAggregationInput | Prisma.TaskMemberOrderByWithAggregationInput[] + by: Prisma.TaskMemberScalarFieldEnum[] | Prisma.TaskMemberScalarFieldEnum + having?: Prisma.TaskMemberScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: TaskMemberCountAggregateInputType | true + _avg?: TaskMemberAvgAggregateInputType + _sum?: TaskMemberSumAggregateInputType + _min?: TaskMemberMinAggregateInputType + _max?: TaskMemberMaxAggregateInputType +} + +export type TaskMemberGroupByOutputType = { + id: number + uuid: string + taskUuid: string | null + taskId: number + userId: number + _count: TaskMemberCountAggregateOutputType | null + _avg: TaskMemberAvgAggregateOutputType | null + _sum: TaskMemberSumAggregateOutputType | null + _min: TaskMemberMinAggregateOutputType | null + _max: TaskMemberMaxAggregateOutputType | null +} + +export type GetTaskMemberGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof TaskMemberGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type TaskMemberWhereInput = { + AND?: Prisma.TaskMemberWhereInput | Prisma.TaskMemberWhereInput[] + OR?: Prisma.TaskMemberWhereInput[] + NOT?: Prisma.TaskMemberWhereInput | Prisma.TaskMemberWhereInput[] + id?: Prisma.IntFilter<"TaskMember"> | number + uuid?: Prisma.StringFilter<"TaskMember"> | string + taskUuid?: Prisma.StringNullableFilter<"TaskMember"> | string | null + taskId?: Prisma.IntFilter<"TaskMember"> | number + userId?: Prisma.IntFilter<"TaskMember"> | number + task?: Prisma.XOR + user?: Prisma.XOR +} + +export type TaskMemberOrderByWithRelationInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + taskUuid?: Prisma.SortOrderInput | Prisma.SortOrder + taskId?: Prisma.SortOrder + userId?: Prisma.SortOrder + task?: Prisma.TaskOrderByWithRelationInput + user?: Prisma.UserOrderByWithRelationInput +} + +export type TaskMemberWhereUniqueInput = Prisma.AtLeast<{ + id?: number + uuid?: string + taskId_userId?: Prisma.TaskMemberTaskIdUserIdCompoundUniqueInput + AND?: Prisma.TaskMemberWhereInput | Prisma.TaskMemberWhereInput[] + OR?: Prisma.TaskMemberWhereInput[] + NOT?: Prisma.TaskMemberWhereInput | Prisma.TaskMemberWhereInput[] + taskUuid?: Prisma.StringNullableFilter<"TaskMember"> | string | null + taskId?: Prisma.IntFilter<"TaskMember"> | number + userId?: Prisma.IntFilter<"TaskMember"> | number + task?: Prisma.XOR + user?: Prisma.XOR +}, "id" | "uuid" | "taskId_userId"> + +export type TaskMemberOrderByWithAggregationInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + taskUuid?: Prisma.SortOrderInput | Prisma.SortOrder + taskId?: Prisma.SortOrder + userId?: Prisma.SortOrder + _count?: Prisma.TaskMemberCountOrderByAggregateInput + _avg?: Prisma.TaskMemberAvgOrderByAggregateInput + _max?: Prisma.TaskMemberMaxOrderByAggregateInput + _min?: Prisma.TaskMemberMinOrderByAggregateInput + _sum?: Prisma.TaskMemberSumOrderByAggregateInput +} + +export type TaskMemberScalarWhereWithAggregatesInput = { + AND?: Prisma.TaskMemberScalarWhereWithAggregatesInput | Prisma.TaskMemberScalarWhereWithAggregatesInput[] + OR?: Prisma.TaskMemberScalarWhereWithAggregatesInput[] + NOT?: Prisma.TaskMemberScalarWhereWithAggregatesInput | Prisma.TaskMemberScalarWhereWithAggregatesInput[] + id?: Prisma.IntWithAggregatesFilter<"TaskMember"> | number + uuid?: Prisma.StringWithAggregatesFilter<"TaskMember"> | string + taskUuid?: Prisma.StringNullableWithAggregatesFilter<"TaskMember"> | string | null + taskId?: Prisma.IntWithAggregatesFilter<"TaskMember"> | number + userId?: Prisma.IntWithAggregatesFilter<"TaskMember"> | number +} + +export type TaskMemberCreateInput = { + uuid: string + taskUuid?: string | null + task: Prisma.TaskCreateNestedOneWithoutMembersInput + user: Prisma.UserCreateNestedOneWithoutTaskMembersInput +} + +export type TaskMemberUncheckedCreateInput = { + id?: number + uuid: string + taskUuid?: string | null + taskId: number + userId: number +} + +export type TaskMemberUpdateInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + taskUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + task?: Prisma.TaskUpdateOneRequiredWithoutMembersNestedInput + user?: Prisma.UserUpdateOneRequiredWithoutTaskMembersNestedInput +} + +export type TaskMemberUncheckedUpdateInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + taskUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + taskId?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.IntFieldUpdateOperationsInput | number +} + +export type TaskMemberCreateManyInput = { + id?: number + uuid: string + taskUuid?: string | null + taskId: number + userId: number +} + +export type TaskMemberUpdateManyMutationInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + taskUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null +} + +export type TaskMemberUncheckedUpdateManyInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + taskUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + taskId?: Prisma.IntFieldUpdateOperationsInput | number + userId?: Prisma.IntFieldUpdateOperationsInput | number +} + +export type TaskMemberListRelationFilter = { + every?: Prisma.TaskMemberWhereInput + some?: Prisma.TaskMemberWhereInput + none?: Prisma.TaskMemberWhereInput +} + +export type TaskMemberOrderByRelationAggregateInput = { + _count?: Prisma.SortOrder +} + +export type TaskMemberTaskIdUserIdCompoundUniqueInput = { + taskId: number + userId: number +} + +export type TaskMemberCountOrderByAggregateInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + taskUuid?: Prisma.SortOrder + taskId?: Prisma.SortOrder + userId?: Prisma.SortOrder +} + +export type TaskMemberAvgOrderByAggregateInput = { + id?: Prisma.SortOrder + taskId?: Prisma.SortOrder + userId?: Prisma.SortOrder +} + +export type TaskMemberMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + taskUuid?: Prisma.SortOrder + taskId?: Prisma.SortOrder + userId?: Prisma.SortOrder +} + +export type TaskMemberMinOrderByAggregateInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + taskUuid?: Prisma.SortOrder + taskId?: Prisma.SortOrder + userId?: Prisma.SortOrder +} + +export type TaskMemberSumOrderByAggregateInput = { + id?: Prisma.SortOrder + taskId?: Prisma.SortOrder + userId?: Prisma.SortOrder +} + +export type TaskMemberCreateNestedManyWithoutUserInput = { + create?: Prisma.XOR | Prisma.TaskMemberCreateWithoutUserInput[] | Prisma.TaskMemberUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.TaskMemberCreateOrConnectWithoutUserInput | Prisma.TaskMemberCreateOrConnectWithoutUserInput[] + createMany?: Prisma.TaskMemberCreateManyUserInputEnvelope + connect?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] +} + +export type TaskMemberUncheckedCreateNestedManyWithoutUserInput = { + create?: Prisma.XOR | Prisma.TaskMemberCreateWithoutUserInput[] | Prisma.TaskMemberUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.TaskMemberCreateOrConnectWithoutUserInput | Prisma.TaskMemberCreateOrConnectWithoutUserInput[] + createMany?: Prisma.TaskMemberCreateManyUserInputEnvelope + connect?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] +} + +export type TaskMemberUpdateManyWithoutUserNestedInput = { + create?: Prisma.XOR | Prisma.TaskMemberCreateWithoutUserInput[] | Prisma.TaskMemberUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.TaskMemberCreateOrConnectWithoutUserInput | Prisma.TaskMemberCreateOrConnectWithoutUserInput[] + upsert?: Prisma.TaskMemberUpsertWithWhereUniqueWithoutUserInput | Prisma.TaskMemberUpsertWithWhereUniqueWithoutUserInput[] + createMany?: Prisma.TaskMemberCreateManyUserInputEnvelope + set?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] + disconnect?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] + delete?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] + connect?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] + update?: Prisma.TaskMemberUpdateWithWhereUniqueWithoutUserInput | Prisma.TaskMemberUpdateWithWhereUniqueWithoutUserInput[] + updateMany?: Prisma.TaskMemberUpdateManyWithWhereWithoutUserInput | Prisma.TaskMemberUpdateManyWithWhereWithoutUserInput[] + deleteMany?: Prisma.TaskMemberScalarWhereInput | Prisma.TaskMemberScalarWhereInput[] +} + +export type TaskMemberUncheckedUpdateManyWithoutUserNestedInput = { + create?: Prisma.XOR | Prisma.TaskMemberCreateWithoutUserInput[] | Prisma.TaskMemberUncheckedCreateWithoutUserInput[] + connectOrCreate?: Prisma.TaskMemberCreateOrConnectWithoutUserInput | Prisma.TaskMemberCreateOrConnectWithoutUserInput[] + upsert?: Prisma.TaskMemberUpsertWithWhereUniqueWithoutUserInput | Prisma.TaskMemberUpsertWithWhereUniqueWithoutUserInput[] + createMany?: Prisma.TaskMemberCreateManyUserInputEnvelope + set?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] + disconnect?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] + delete?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] + connect?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] + update?: Prisma.TaskMemberUpdateWithWhereUniqueWithoutUserInput | Prisma.TaskMemberUpdateWithWhereUniqueWithoutUserInput[] + updateMany?: Prisma.TaskMemberUpdateManyWithWhereWithoutUserInput | Prisma.TaskMemberUpdateManyWithWhereWithoutUserInput[] + deleteMany?: Prisma.TaskMemberScalarWhereInput | Prisma.TaskMemberScalarWhereInput[] +} + +export type TaskMemberCreateNestedManyWithoutTaskInput = { + create?: Prisma.XOR | Prisma.TaskMemberCreateWithoutTaskInput[] | Prisma.TaskMemberUncheckedCreateWithoutTaskInput[] + connectOrCreate?: Prisma.TaskMemberCreateOrConnectWithoutTaskInput | Prisma.TaskMemberCreateOrConnectWithoutTaskInput[] + createMany?: Prisma.TaskMemberCreateManyTaskInputEnvelope + connect?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] +} + +export type TaskMemberUncheckedCreateNestedManyWithoutTaskInput = { + create?: Prisma.XOR | Prisma.TaskMemberCreateWithoutTaskInput[] | Prisma.TaskMemberUncheckedCreateWithoutTaskInput[] + connectOrCreate?: Prisma.TaskMemberCreateOrConnectWithoutTaskInput | Prisma.TaskMemberCreateOrConnectWithoutTaskInput[] + createMany?: Prisma.TaskMemberCreateManyTaskInputEnvelope + connect?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] +} + +export type TaskMemberUpdateManyWithoutTaskNestedInput = { + create?: Prisma.XOR | Prisma.TaskMemberCreateWithoutTaskInput[] | Prisma.TaskMemberUncheckedCreateWithoutTaskInput[] + connectOrCreate?: Prisma.TaskMemberCreateOrConnectWithoutTaskInput | Prisma.TaskMemberCreateOrConnectWithoutTaskInput[] + upsert?: Prisma.TaskMemberUpsertWithWhereUniqueWithoutTaskInput | Prisma.TaskMemberUpsertWithWhereUniqueWithoutTaskInput[] + createMany?: Prisma.TaskMemberCreateManyTaskInputEnvelope + set?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] + disconnect?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] + delete?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] + connect?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] + update?: Prisma.TaskMemberUpdateWithWhereUniqueWithoutTaskInput | Prisma.TaskMemberUpdateWithWhereUniqueWithoutTaskInput[] + updateMany?: Prisma.TaskMemberUpdateManyWithWhereWithoutTaskInput | Prisma.TaskMemberUpdateManyWithWhereWithoutTaskInput[] + deleteMany?: Prisma.TaskMemberScalarWhereInput | Prisma.TaskMemberScalarWhereInput[] +} + +export type TaskMemberUncheckedUpdateManyWithoutTaskNestedInput = { + create?: Prisma.XOR | Prisma.TaskMemberCreateWithoutTaskInput[] | Prisma.TaskMemberUncheckedCreateWithoutTaskInput[] + connectOrCreate?: Prisma.TaskMemberCreateOrConnectWithoutTaskInput | Prisma.TaskMemberCreateOrConnectWithoutTaskInput[] + upsert?: Prisma.TaskMemberUpsertWithWhereUniqueWithoutTaskInput | Prisma.TaskMemberUpsertWithWhereUniqueWithoutTaskInput[] + createMany?: Prisma.TaskMemberCreateManyTaskInputEnvelope + set?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] + disconnect?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] + delete?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] + connect?: Prisma.TaskMemberWhereUniqueInput | Prisma.TaskMemberWhereUniqueInput[] + update?: Prisma.TaskMemberUpdateWithWhereUniqueWithoutTaskInput | Prisma.TaskMemberUpdateWithWhereUniqueWithoutTaskInput[] + updateMany?: Prisma.TaskMemberUpdateManyWithWhereWithoutTaskInput | Prisma.TaskMemberUpdateManyWithWhereWithoutTaskInput[] + deleteMany?: Prisma.TaskMemberScalarWhereInput | Prisma.TaskMemberScalarWhereInput[] +} + +export type TaskMemberCreateWithoutUserInput = { + uuid: string + taskUuid?: string | null + task: Prisma.TaskCreateNestedOneWithoutMembersInput +} + +export type TaskMemberUncheckedCreateWithoutUserInput = { + id?: number + uuid: string + taskUuid?: string | null + taskId: number +} + +export type TaskMemberCreateOrConnectWithoutUserInput = { + where: Prisma.TaskMemberWhereUniqueInput + create: Prisma.XOR +} + +export type TaskMemberCreateManyUserInputEnvelope = { + data: Prisma.TaskMemberCreateManyUserInput | Prisma.TaskMemberCreateManyUserInput[] + skipDuplicates?: boolean +} + +export type TaskMemberUpsertWithWhereUniqueWithoutUserInput = { + where: Prisma.TaskMemberWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type TaskMemberUpdateWithWhereUniqueWithoutUserInput = { + where: Prisma.TaskMemberWhereUniqueInput + data: Prisma.XOR +} + +export type TaskMemberUpdateManyWithWhereWithoutUserInput = { + where: Prisma.TaskMemberScalarWhereInput + data: Prisma.XOR +} + +export type TaskMemberScalarWhereInput = { + AND?: Prisma.TaskMemberScalarWhereInput | Prisma.TaskMemberScalarWhereInput[] + OR?: Prisma.TaskMemberScalarWhereInput[] + NOT?: Prisma.TaskMemberScalarWhereInput | Prisma.TaskMemberScalarWhereInput[] + id?: Prisma.IntFilter<"TaskMember"> | number + uuid?: Prisma.StringFilter<"TaskMember"> | string + taskUuid?: Prisma.StringNullableFilter<"TaskMember"> | string | null + taskId?: Prisma.IntFilter<"TaskMember"> | number + userId?: Prisma.IntFilter<"TaskMember"> | number +} + +export type TaskMemberCreateWithoutTaskInput = { + uuid: string + taskUuid?: string | null + user: Prisma.UserCreateNestedOneWithoutTaskMembersInput +} + +export type TaskMemberUncheckedCreateWithoutTaskInput = { + id?: number + uuid: string + taskUuid?: string | null + userId: number +} + +export type TaskMemberCreateOrConnectWithoutTaskInput = { + where: Prisma.TaskMemberWhereUniqueInput + create: Prisma.XOR +} + +export type TaskMemberCreateManyTaskInputEnvelope = { + data: Prisma.TaskMemberCreateManyTaskInput | Prisma.TaskMemberCreateManyTaskInput[] + skipDuplicates?: boolean +} + +export type TaskMemberUpsertWithWhereUniqueWithoutTaskInput = { + where: Prisma.TaskMemberWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type TaskMemberUpdateWithWhereUniqueWithoutTaskInput = { + where: Prisma.TaskMemberWhereUniqueInput + data: Prisma.XOR +} + +export type TaskMemberUpdateManyWithWhereWithoutTaskInput = { + where: Prisma.TaskMemberScalarWhereInput + data: Prisma.XOR +} + +export type TaskMemberCreateManyUserInput = { + id?: number + uuid: string + taskUuid?: string | null + taskId: number +} + +export type TaskMemberUpdateWithoutUserInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + taskUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + task?: Prisma.TaskUpdateOneRequiredWithoutMembersNestedInput +} + +export type TaskMemberUncheckedUpdateWithoutUserInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + taskUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + taskId?: Prisma.IntFieldUpdateOperationsInput | number +} + +export type TaskMemberUncheckedUpdateManyWithoutUserInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + taskUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + taskId?: Prisma.IntFieldUpdateOperationsInput | number +} + +export type TaskMemberCreateManyTaskInput = { + id?: number + uuid: string + taskUuid?: string | null + userId: number +} + +export type TaskMemberUpdateWithoutTaskInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + taskUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + user?: Prisma.UserUpdateOneRequiredWithoutTaskMembersNestedInput +} + +export type TaskMemberUncheckedUpdateWithoutTaskInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + taskUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + userId?: Prisma.IntFieldUpdateOperationsInput | number +} + +export type TaskMemberUncheckedUpdateManyWithoutTaskInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + taskUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + userId?: Prisma.IntFieldUpdateOperationsInput | number +} + + + +export type TaskMemberSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + taskUuid?: boolean + taskId?: boolean + userId?: boolean + task?: boolean | Prisma.TaskDefaultArgs + user?: boolean | Prisma.UserDefaultArgs +}, ExtArgs["result"]["taskMember"]> + +export type TaskMemberSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + taskUuid?: boolean + taskId?: boolean + userId?: boolean + task?: boolean | Prisma.TaskDefaultArgs + user?: boolean | Prisma.UserDefaultArgs +}, ExtArgs["result"]["taskMember"]> + +export type TaskMemberSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + taskUuid?: boolean + taskId?: boolean + userId?: boolean + task?: boolean | Prisma.TaskDefaultArgs + user?: boolean | Prisma.UserDefaultArgs +}, ExtArgs["result"]["taskMember"]> + +export type TaskMemberSelectScalar = { + id?: boolean + uuid?: boolean + taskUuid?: boolean + taskId?: boolean + userId?: boolean +} + +export type TaskMemberOmit = runtime.Types.Extensions.GetOmit<"id" | "uuid" | "taskUuid" | "taskId" | "userId", ExtArgs["result"]["taskMember"]> +export type TaskMemberInclude = { + task?: boolean | Prisma.TaskDefaultArgs + user?: boolean | Prisma.UserDefaultArgs +} +export type TaskMemberIncludeCreateManyAndReturn = { + task?: boolean | Prisma.TaskDefaultArgs + user?: boolean | Prisma.UserDefaultArgs +} +export type TaskMemberIncludeUpdateManyAndReturn = { + task?: boolean | Prisma.TaskDefaultArgs + user?: boolean | Prisma.UserDefaultArgs +} + +export type $TaskMemberPayload = { + name: "TaskMember" + objects: { + task: Prisma.$TaskPayload + user: Prisma.$UserPayload + } + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: number + uuid: string + taskUuid: string | null + taskId: number + userId: number + }, ExtArgs["result"]["taskMember"]> + composites: {} +} + +export type TaskMemberGetPayload = runtime.Types.Result.GetResult + +export type TaskMemberCountArgs = + Omit & { + select?: TaskMemberCountAggregateInputType | true + } + +export interface TaskMemberDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['TaskMember'], meta: { name: 'TaskMember' } } + /** + * Find zero or one TaskMember that matches the filter. + * @param {TaskMemberFindUniqueArgs} args - Arguments to find a TaskMember + * @example + * // Get one TaskMember + * const taskMember = await prisma.taskMember.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__TaskMemberClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one TaskMember that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {TaskMemberFindUniqueOrThrowArgs} args - Arguments to find a TaskMember + * @example + * // Get one TaskMember + * const taskMember = await prisma.taskMember.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__TaskMemberClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first TaskMember that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskMemberFindFirstArgs} args - Arguments to find a TaskMember + * @example + * // Get one TaskMember + * const taskMember = await prisma.taskMember.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__TaskMemberClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first TaskMember that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskMemberFindFirstOrThrowArgs} args - Arguments to find a TaskMember + * @example + * // Get one TaskMember + * const taskMember = await prisma.taskMember.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__TaskMemberClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more TaskMembers that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskMemberFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all TaskMembers + * const taskMembers = await prisma.taskMember.findMany() + * + * // Get first 10 TaskMembers + * const taskMembers = await prisma.taskMember.findMany({ take: 10 }) + * + * // Only select the `id` + * const taskMemberWithIdOnly = await prisma.taskMember.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a TaskMember. + * @param {TaskMemberCreateArgs} args - Arguments to create a TaskMember. + * @example + * // Create one TaskMember + * const TaskMember = await prisma.taskMember.create({ + * data: { + * // ... data to create a TaskMember + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__TaskMemberClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many TaskMembers. + * @param {TaskMemberCreateManyArgs} args - Arguments to create many TaskMembers. + * @example + * // Create many TaskMembers + * const taskMember = await prisma.taskMember.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many TaskMembers and returns the data saved in the database. + * @param {TaskMemberCreateManyAndReturnArgs} args - Arguments to create many TaskMembers. + * @example + * // Create many TaskMembers + * const taskMember = await prisma.taskMember.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many TaskMembers and only return the `id` + * const taskMemberWithIdOnly = await prisma.taskMember.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a TaskMember. + * @param {TaskMemberDeleteArgs} args - Arguments to delete one TaskMember. + * @example + * // Delete one TaskMember + * const TaskMember = await prisma.taskMember.delete({ + * where: { + * // ... filter to delete one TaskMember + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__TaskMemberClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one TaskMember. + * @param {TaskMemberUpdateArgs} args - Arguments to update one TaskMember. + * @example + * // Update one TaskMember + * const taskMember = await prisma.taskMember.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__TaskMemberClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more TaskMembers. + * @param {TaskMemberDeleteManyArgs} args - Arguments to filter TaskMembers to delete. + * @example + * // Delete a few TaskMembers + * const { count } = await prisma.taskMember.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more TaskMembers. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskMemberUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many TaskMembers + * const taskMember = await prisma.taskMember.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more TaskMembers and returns the data updated in the database. + * @param {TaskMemberUpdateManyAndReturnArgs} args - Arguments to update many TaskMembers. + * @example + * // Update many TaskMembers + * const taskMember = await prisma.taskMember.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more TaskMembers and only return the `id` + * const taskMemberWithIdOnly = await prisma.taskMember.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one TaskMember. + * @param {TaskMemberUpsertArgs} args - Arguments to update or create a TaskMember. + * @example + * // Update or create a TaskMember + * const taskMember = await prisma.taskMember.upsert({ + * create: { + * // ... data to create a TaskMember + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the TaskMember we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__TaskMemberClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of TaskMembers. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskMemberCountArgs} args - Arguments to filter TaskMembers to count. + * @example + * // Count the number of TaskMembers + * const count = await prisma.taskMember.count({ + * where: { + * // ... the filter for the TaskMembers we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a TaskMember. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskMemberAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by TaskMember. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskMemberGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends TaskMemberGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: TaskMemberGroupByArgs['orderBy'] } + : { orderBy?: TaskMemberGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetTaskMemberGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the TaskMember model + */ +readonly fields: TaskMemberFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for TaskMember. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__TaskMemberClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + task = {}>(args?: Prisma.Subset>): Prisma.Prisma__TaskClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + user = {}>(args?: Prisma.Subset>): Prisma.Prisma__UserClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the TaskMember model + */ +export interface TaskMemberFieldRefs { + readonly id: Prisma.FieldRef<"TaskMember", 'Int'> + readonly uuid: Prisma.FieldRef<"TaskMember", 'String'> + readonly taskUuid: Prisma.FieldRef<"TaskMember", 'String'> + readonly taskId: Prisma.FieldRef<"TaskMember", 'Int'> + readonly userId: Prisma.FieldRef<"TaskMember", 'Int'> +} + + +// Custom InputTypes +/** + * TaskMember findUnique + */ +export type TaskMemberFindUniqueArgs = { + /** + * Select specific fields to fetch from the TaskMember + */ + select?: Prisma.TaskMemberSelect | null + /** + * Omit specific fields from the TaskMember + */ + omit?: Prisma.TaskMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskMemberInclude | null + /** + * Filter, which TaskMember to fetch. + */ + where: Prisma.TaskMemberWhereUniqueInput +} + +/** + * TaskMember findUniqueOrThrow + */ +export type TaskMemberFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the TaskMember + */ + select?: Prisma.TaskMemberSelect | null + /** + * Omit specific fields from the TaskMember + */ + omit?: Prisma.TaskMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskMemberInclude | null + /** + * Filter, which TaskMember to fetch. + */ + where: Prisma.TaskMemberWhereUniqueInput +} + +/** + * TaskMember findFirst + */ +export type TaskMemberFindFirstArgs = { + /** + * Select specific fields to fetch from the TaskMember + */ + select?: Prisma.TaskMemberSelect | null + /** + * Omit specific fields from the TaskMember + */ + omit?: Prisma.TaskMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskMemberInclude | null + /** + * Filter, which TaskMember to fetch. + */ + where?: Prisma.TaskMemberWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of TaskMembers to fetch. + */ + orderBy?: Prisma.TaskMemberOrderByWithRelationInput | Prisma.TaskMemberOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for TaskMembers. + */ + cursor?: Prisma.TaskMemberWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` TaskMembers from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` TaskMembers. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of TaskMembers. + */ + distinct?: Prisma.TaskMemberScalarFieldEnum | Prisma.TaskMemberScalarFieldEnum[] +} + +/** + * TaskMember findFirstOrThrow + */ +export type TaskMemberFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the TaskMember + */ + select?: Prisma.TaskMemberSelect | null + /** + * Omit specific fields from the TaskMember + */ + omit?: Prisma.TaskMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskMemberInclude | null + /** + * Filter, which TaskMember to fetch. + */ + where?: Prisma.TaskMemberWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of TaskMembers to fetch. + */ + orderBy?: Prisma.TaskMemberOrderByWithRelationInput | Prisma.TaskMemberOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for TaskMembers. + */ + cursor?: Prisma.TaskMemberWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` TaskMembers from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` TaskMembers. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of TaskMembers. + */ + distinct?: Prisma.TaskMemberScalarFieldEnum | Prisma.TaskMemberScalarFieldEnum[] +} + +/** + * TaskMember findMany + */ +export type TaskMemberFindManyArgs = { + /** + * Select specific fields to fetch from the TaskMember + */ + select?: Prisma.TaskMemberSelect | null + /** + * Omit specific fields from the TaskMember + */ + omit?: Prisma.TaskMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskMemberInclude | null + /** + * Filter, which TaskMembers to fetch. + */ + where?: Prisma.TaskMemberWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of TaskMembers to fetch. + */ + orderBy?: Prisma.TaskMemberOrderByWithRelationInput | Prisma.TaskMemberOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing TaskMembers. + */ + cursor?: Prisma.TaskMemberWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` TaskMembers from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` TaskMembers. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of TaskMembers. + */ + distinct?: Prisma.TaskMemberScalarFieldEnum | Prisma.TaskMemberScalarFieldEnum[] +} + +/** + * TaskMember create + */ +export type TaskMemberCreateArgs = { + /** + * Select specific fields to fetch from the TaskMember + */ + select?: Prisma.TaskMemberSelect | null + /** + * Omit specific fields from the TaskMember + */ + omit?: Prisma.TaskMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskMemberInclude | null + /** + * The data needed to create a TaskMember. + */ + data: Prisma.XOR +} + +/** + * TaskMember createMany + */ +export type TaskMemberCreateManyArgs = { + /** + * The data used to create many TaskMembers. + */ + data: Prisma.TaskMemberCreateManyInput | Prisma.TaskMemberCreateManyInput[] + skipDuplicates?: boolean +} + +/** + * TaskMember createManyAndReturn + */ +export type TaskMemberCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the TaskMember + */ + select?: Prisma.TaskMemberSelectCreateManyAndReturn | null + /** + * Omit specific fields from the TaskMember + */ + omit?: Prisma.TaskMemberOmit | null + /** + * The data used to create many TaskMembers. + */ + data: Prisma.TaskMemberCreateManyInput | Prisma.TaskMemberCreateManyInput[] + skipDuplicates?: boolean + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskMemberIncludeCreateManyAndReturn | null +} + +/** + * TaskMember update + */ +export type TaskMemberUpdateArgs = { + /** + * Select specific fields to fetch from the TaskMember + */ + select?: Prisma.TaskMemberSelect | null + /** + * Omit specific fields from the TaskMember + */ + omit?: Prisma.TaskMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskMemberInclude | null + /** + * The data needed to update a TaskMember. + */ + data: Prisma.XOR + /** + * Choose, which TaskMember to update. + */ + where: Prisma.TaskMemberWhereUniqueInput +} + +/** + * TaskMember updateMany + */ +export type TaskMemberUpdateManyArgs = { + /** + * The data used to update TaskMembers. + */ + data: Prisma.XOR + /** + * Filter which TaskMembers to update + */ + where?: Prisma.TaskMemberWhereInput + /** + * Limit how many TaskMembers to update. + */ + limit?: number +} + +/** + * TaskMember updateManyAndReturn + */ +export type TaskMemberUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the TaskMember + */ + select?: Prisma.TaskMemberSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the TaskMember + */ + omit?: Prisma.TaskMemberOmit | null + /** + * The data used to update TaskMembers. + */ + data: Prisma.XOR + /** + * Filter which TaskMembers to update + */ + where?: Prisma.TaskMemberWhereInput + /** + * Limit how many TaskMembers to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskMemberIncludeUpdateManyAndReturn | null +} + +/** + * TaskMember upsert + */ +export type TaskMemberUpsertArgs = { + /** + * Select specific fields to fetch from the TaskMember + */ + select?: Prisma.TaskMemberSelect | null + /** + * Omit specific fields from the TaskMember + */ + omit?: Prisma.TaskMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskMemberInclude | null + /** + * The filter to search for the TaskMember to update in case it exists. + */ + where: Prisma.TaskMemberWhereUniqueInput + /** + * In case the TaskMember found by the `where` argument doesn't exist, create a new TaskMember with this data. + */ + create: Prisma.XOR + /** + * In case the TaskMember was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * TaskMember delete + */ +export type TaskMemberDeleteArgs = { + /** + * Select specific fields to fetch from the TaskMember + */ + select?: Prisma.TaskMemberSelect | null + /** + * Omit specific fields from the TaskMember + */ + omit?: Prisma.TaskMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskMemberInclude | null + /** + * Filter which TaskMember to delete. + */ + where: Prisma.TaskMemberWhereUniqueInput +} + +/** + * TaskMember deleteMany + */ +export type TaskMemberDeleteManyArgs = { + /** + * Filter which TaskMembers to delete + */ + where?: Prisma.TaskMemberWhereInput + /** + * Limit how many TaskMembers to delete. + */ + limit?: number +} + +/** + * TaskMember without action + */ +export type TaskMemberDefaultArgs = { + /** + * Select specific fields to fetch from the TaskMember + */ + select?: Prisma.TaskMemberSelect | null + /** + * Omit specific fields from the TaskMember + */ + omit?: Prisma.TaskMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskMemberInclude | null +} diff --git a/generated/prisma/models/TaskTag.ts b/generated/prisma/models/TaskTag.ts new file mode 100644 index 0000000..30bbb35 --- /dev/null +++ b/generated/prisma/models/TaskTag.ts @@ -0,0 +1,1390 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `TaskTag` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums.ts" +import type * as Prisma from "../internal/prismaNamespace.ts" + +/** + * Model TaskTag + * + */ +export type TaskTagModel = runtime.Types.Result.DefaultSelection + +export type AggregateTaskTag = { + _count: TaskTagCountAggregateOutputType | null + _avg: TaskTagAvgAggregateOutputType | null + _sum: TaskTagSumAggregateOutputType | null + _min: TaskTagMinAggregateOutputType | null + _max: TaskTagMaxAggregateOutputType | null +} + +export type TaskTagAvgAggregateOutputType = { + id: number | null + taskId: number | null +} + +export type TaskTagSumAggregateOutputType = { + id: number | null + taskId: number | null +} + +export type TaskTagMinAggregateOutputType = { + id: number | null + uuid: string | null + taskUuid: string | null + taskId: number | null + name: string | null +} + +export type TaskTagMaxAggregateOutputType = { + id: number | null + uuid: string | null + taskUuid: string | null + taskId: number | null + name: string | null +} + +export type TaskTagCountAggregateOutputType = { + id: number + uuid: number + taskUuid: number + taskId: number + name: number + _all: number +} + + +export type TaskTagAvgAggregateInputType = { + id?: true + taskId?: true +} + +export type TaskTagSumAggregateInputType = { + id?: true + taskId?: true +} + +export type TaskTagMinAggregateInputType = { + id?: true + uuid?: true + taskUuid?: true + taskId?: true + name?: true +} + +export type TaskTagMaxAggregateInputType = { + id?: true + uuid?: true + taskUuid?: true + taskId?: true + name?: true +} + +export type TaskTagCountAggregateInputType = { + id?: true + uuid?: true + taskUuid?: true + taskId?: true + name?: true + _all?: true +} + +export type TaskTagAggregateArgs = { + /** + * Filter which TaskTag to aggregate. + */ + where?: Prisma.TaskTagWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of TaskTags to fetch. + */ + orderBy?: Prisma.TaskTagOrderByWithRelationInput | Prisma.TaskTagOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.TaskTagWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` TaskTags from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` TaskTags. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned TaskTags + **/ + _count?: true | TaskTagCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to average + **/ + _avg?: TaskTagAvgAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to sum + **/ + _sum?: TaskTagSumAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: TaskTagMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: TaskTagMaxAggregateInputType +} + +export type GetTaskTagAggregateType = { + [P in keyof T & keyof AggregateTaskTag]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type TaskTagGroupByArgs = { + where?: Prisma.TaskTagWhereInput + orderBy?: Prisma.TaskTagOrderByWithAggregationInput | Prisma.TaskTagOrderByWithAggregationInput[] + by: Prisma.TaskTagScalarFieldEnum[] | Prisma.TaskTagScalarFieldEnum + having?: Prisma.TaskTagScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: TaskTagCountAggregateInputType | true + _avg?: TaskTagAvgAggregateInputType + _sum?: TaskTagSumAggregateInputType + _min?: TaskTagMinAggregateInputType + _max?: TaskTagMaxAggregateInputType +} + +export type TaskTagGroupByOutputType = { + id: number + uuid: string + taskUuid: string | null + taskId: number + name: string + _count: TaskTagCountAggregateOutputType | null + _avg: TaskTagAvgAggregateOutputType | null + _sum: TaskTagSumAggregateOutputType | null + _min: TaskTagMinAggregateOutputType | null + _max: TaskTagMaxAggregateOutputType | null +} + +export type GetTaskTagGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof TaskTagGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type TaskTagWhereInput = { + AND?: Prisma.TaskTagWhereInput | Prisma.TaskTagWhereInput[] + OR?: Prisma.TaskTagWhereInput[] + NOT?: Prisma.TaskTagWhereInput | Prisma.TaskTagWhereInput[] + id?: Prisma.IntFilter<"TaskTag"> | number + uuid?: Prisma.StringFilter<"TaskTag"> | string + taskUuid?: Prisma.StringNullableFilter<"TaskTag"> | string | null + taskId?: Prisma.IntFilter<"TaskTag"> | number + name?: Prisma.StringFilter<"TaskTag"> | string + task?: Prisma.XOR +} + +export type TaskTagOrderByWithRelationInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + taskUuid?: Prisma.SortOrderInput | Prisma.SortOrder + taskId?: Prisma.SortOrder + name?: Prisma.SortOrder + task?: Prisma.TaskOrderByWithRelationInput +} + +export type TaskTagWhereUniqueInput = Prisma.AtLeast<{ + id?: number + uuid?: string + AND?: Prisma.TaskTagWhereInput | Prisma.TaskTagWhereInput[] + OR?: Prisma.TaskTagWhereInput[] + NOT?: Prisma.TaskTagWhereInput | Prisma.TaskTagWhereInput[] + taskUuid?: Prisma.StringNullableFilter<"TaskTag"> | string | null + taskId?: Prisma.IntFilter<"TaskTag"> | number + name?: Prisma.StringFilter<"TaskTag"> | string + task?: Prisma.XOR +}, "id" | "uuid"> + +export type TaskTagOrderByWithAggregationInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + taskUuid?: Prisma.SortOrderInput | Prisma.SortOrder + taskId?: Prisma.SortOrder + name?: Prisma.SortOrder + _count?: Prisma.TaskTagCountOrderByAggregateInput + _avg?: Prisma.TaskTagAvgOrderByAggregateInput + _max?: Prisma.TaskTagMaxOrderByAggregateInput + _min?: Prisma.TaskTagMinOrderByAggregateInput + _sum?: Prisma.TaskTagSumOrderByAggregateInput +} + +export type TaskTagScalarWhereWithAggregatesInput = { + AND?: Prisma.TaskTagScalarWhereWithAggregatesInput | Prisma.TaskTagScalarWhereWithAggregatesInput[] + OR?: Prisma.TaskTagScalarWhereWithAggregatesInput[] + NOT?: Prisma.TaskTagScalarWhereWithAggregatesInput | Prisma.TaskTagScalarWhereWithAggregatesInput[] + id?: Prisma.IntWithAggregatesFilter<"TaskTag"> | number + uuid?: Prisma.StringWithAggregatesFilter<"TaskTag"> | string + taskUuid?: Prisma.StringNullableWithAggregatesFilter<"TaskTag"> | string | null + taskId?: Prisma.IntWithAggregatesFilter<"TaskTag"> | number + name?: Prisma.StringWithAggregatesFilter<"TaskTag"> | string +} + +export type TaskTagCreateInput = { + uuid: string + taskUuid?: string | null + name: string + task: Prisma.TaskCreateNestedOneWithoutTagsInput +} + +export type TaskTagUncheckedCreateInput = { + id?: number + uuid: string + taskUuid?: string | null + taskId: number + name: string +} + +export type TaskTagUpdateInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + taskUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + name?: Prisma.StringFieldUpdateOperationsInput | string + task?: Prisma.TaskUpdateOneRequiredWithoutTagsNestedInput +} + +export type TaskTagUncheckedUpdateInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + taskUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + taskId?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string +} + +export type TaskTagCreateManyInput = { + id?: number + uuid: string + taskUuid?: string | null + taskId: number + name: string +} + +export type TaskTagUpdateManyMutationInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + taskUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + name?: Prisma.StringFieldUpdateOperationsInput | string +} + +export type TaskTagUncheckedUpdateManyInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + taskUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + taskId?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string +} + +export type TaskTagListRelationFilter = { + every?: Prisma.TaskTagWhereInput + some?: Prisma.TaskTagWhereInput + none?: Prisma.TaskTagWhereInput +} + +export type TaskTagOrderByRelationAggregateInput = { + _count?: Prisma.SortOrder +} + +export type TaskTagCountOrderByAggregateInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + taskUuid?: Prisma.SortOrder + taskId?: Prisma.SortOrder + name?: Prisma.SortOrder +} + +export type TaskTagAvgOrderByAggregateInput = { + id?: Prisma.SortOrder + taskId?: Prisma.SortOrder +} + +export type TaskTagMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + taskUuid?: Prisma.SortOrder + taskId?: Prisma.SortOrder + name?: Prisma.SortOrder +} + +export type TaskTagMinOrderByAggregateInput = { + id?: Prisma.SortOrder + uuid?: Prisma.SortOrder + taskUuid?: Prisma.SortOrder + taskId?: Prisma.SortOrder + name?: Prisma.SortOrder +} + +export type TaskTagSumOrderByAggregateInput = { + id?: Prisma.SortOrder + taskId?: Prisma.SortOrder +} + +export type TaskTagCreateNestedManyWithoutTaskInput = { + create?: Prisma.XOR | Prisma.TaskTagCreateWithoutTaskInput[] | Prisma.TaskTagUncheckedCreateWithoutTaskInput[] + connectOrCreate?: Prisma.TaskTagCreateOrConnectWithoutTaskInput | Prisma.TaskTagCreateOrConnectWithoutTaskInput[] + createMany?: Prisma.TaskTagCreateManyTaskInputEnvelope + connect?: Prisma.TaskTagWhereUniqueInput | Prisma.TaskTagWhereUniqueInput[] +} + +export type TaskTagUncheckedCreateNestedManyWithoutTaskInput = { + create?: Prisma.XOR | Prisma.TaskTagCreateWithoutTaskInput[] | Prisma.TaskTagUncheckedCreateWithoutTaskInput[] + connectOrCreate?: Prisma.TaskTagCreateOrConnectWithoutTaskInput | Prisma.TaskTagCreateOrConnectWithoutTaskInput[] + createMany?: Prisma.TaskTagCreateManyTaskInputEnvelope + connect?: Prisma.TaskTagWhereUniqueInput | Prisma.TaskTagWhereUniqueInput[] +} + +export type TaskTagUpdateManyWithoutTaskNestedInput = { + create?: Prisma.XOR | Prisma.TaskTagCreateWithoutTaskInput[] | Prisma.TaskTagUncheckedCreateWithoutTaskInput[] + connectOrCreate?: Prisma.TaskTagCreateOrConnectWithoutTaskInput | Prisma.TaskTagCreateOrConnectWithoutTaskInput[] + upsert?: Prisma.TaskTagUpsertWithWhereUniqueWithoutTaskInput | Prisma.TaskTagUpsertWithWhereUniqueWithoutTaskInput[] + createMany?: Prisma.TaskTagCreateManyTaskInputEnvelope + set?: Prisma.TaskTagWhereUniqueInput | Prisma.TaskTagWhereUniqueInput[] + disconnect?: Prisma.TaskTagWhereUniqueInput | Prisma.TaskTagWhereUniqueInput[] + delete?: Prisma.TaskTagWhereUniqueInput | Prisma.TaskTagWhereUniqueInput[] + connect?: Prisma.TaskTagWhereUniqueInput | Prisma.TaskTagWhereUniqueInput[] + update?: Prisma.TaskTagUpdateWithWhereUniqueWithoutTaskInput | Prisma.TaskTagUpdateWithWhereUniqueWithoutTaskInput[] + updateMany?: Prisma.TaskTagUpdateManyWithWhereWithoutTaskInput | Prisma.TaskTagUpdateManyWithWhereWithoutTaskInput[] + deleteMany?: Prisma.TaskTagScalarWhereInput | Prisma.TaskTagScalarWhereInput[] +} + +export type TaskTagUncheckedUpdateManyWithoutTaskNestedInput = { + create?: Prisma.XOR | Prisma.TaskTagCreateWithoutTaskInput[] | Prisma.TaskTagUncheckedCreateWithoutTaskInput[] + connectOrCreate?: Prisma.TaskTagCreateOrConnectWithoutTaskInput | Prisma.TaskTagCreateOrConnectWithoutTaskInput[] + upsert?: Prisma.TaskTagUpsertWithWhereUniqueWithoutTaskInput | Prisma.TaskTagUpsertWithWhereUniqueWithoutTaskInput[] + createMany?: Prisma.TaskTagCreateManyTaskInputEnvelope + set?: Prisma.TaskTagWhereUniqueInput | Prisma.TaskTagWhereUniqueInput[] + disconnect?: Prisma.TaskTagWhereUniqueInput | Prisma.TaskTagWhereUniqueInput[] + delete?: Prisma.TaskTagWhereUniqueInput | Prisma.TaskTagWhereUniqueInput[] + connect?: Prisma.TaskTagWhereUniqueInput | Prisma.TaskTagWhereUniqueInput[] + update?: Prisma.TaskTagUpdateWithWhereUniqueWithoutTaskInput | Prisma.TaskTagUpdateWithWhereUniqueWithoutTaskInput[] + updateMany?: Prisma.TaskTagUpdateManyWithWhereWithoutTaskInput | Prisma.TaskTagUpdateManyWithWhereWithoutTaskInput[] + deleteMany?: Prisma.TaskTagScalarWhereInput | Prisma.TaskTagScalarWhereInput[] +} + +export type TaskTagCreateWithoutTaskInput = { + uuid: string + taskUuid?: string | null + name: string +} + +export type TaskTagUncheckedCreateWithoutTaskInput = { + id?: number + uuid: string + taskUuid?: string | null + name: string +} + +export type TaskTagCreateOrConnectWithoutTaskInput = { + where: Prisma.TaskTagWhereUniqueInput + create: Prisma.XOR +} + +export type TaskTagCreateManyTaskInputEnvelope = { + data: Prisma.TaskTagCreateManyTaskInput | Prisma.TaskTagCreateManyTaskInput[] + skipDuplicates?: boolean +} + +export type TaskTagUpsertWithWhereUniqueWithoutTaskInput = { + where: Prisma.TaskTagWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type TaskTagUpdateWithWhereUniqueWithoutTaskInput = { + where: Prisma.TaskTagWhereUniqueInput + data: Prisma.XOR +} + +export type TaskTagUpdateManyWithWhereWithoutTaskInput = { + where: Prisma.TaskTagScalarWhereInput + data: Prisma.XOR +} + +export type TaskTagScalarWhereInput = { + AND?: Prisma.TaskTagScalarWhereInput | Prisma.TaskTagScalarWhereInput[] + OR?: Prisma.TaskTagScalarWhereInput[] + NOT?: Prisma.TaskTagScalarWhereInput | Prisma.TaskTagScalarWhereInput[] + id?: Prisma.IntFilter<"TaskTag"> | number + uuid?: Prisma.StringFilter<"TaskTag"> | string + taskUuid?: Prisma.StringNullableFilter<"TaskTag"> | string | null + taskId?: Prisma.IntFilter<"TaskTag"> | number + name?: Prisma.StringFilter<"TaskTag"> | string +} + +export type TaskTagCreateManyTaskInput = { + id?: number + uuid: string + taskUuid?: string | null + name: string +} + +export type TaskTagUpdateWithoutTaskInput = { + uuid?: Prisma.StringFieldUpdateOperationsInput | string + taskUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + name?: Prisma.StringFieldUpdateOperationsInput | string +} + +export type TaskTagUncheckedUpdateWithoutTaskInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + taskUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + name?: Prisma.StringFieldUpdateOperationsInput | string +} + +export type TaskTagUncheckedUpdateManyWithoutTaskInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + uuid?: Prisma.StringFieldUpdateOperationsInput | string + taskUuid?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + name?: Prisma.StringFieldUpdateOperationsInput | string +} + + + +export type TaskTagSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + taskUuid?: boolean + taskId?: boolean + name?: boolean + task?: boolean | Prisma.TaskDefaultArgs +}, ExtArgs["result"]["taskTag"]> + +export type TaskTagSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + taskUuid?: boolean + taskId?: boolean + name?: boolean + task?: boolean | Prisma.TaskDefaultArgs +}, ExtArgs["result"]["taskTag"]> + +export type TaskTagSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + uuid?: boolean + taskUuid?: boolean + taskId?: boolean + name?: boolean + task?: boolean | Prisma.TaskDefaultArgs +}, ExtArgs["result"]["taskTag"]> + +export type TaskTagSelectScalar = { + id?: boolean + uuid?: boolean + taskUuid?: boolean + taskId?: boolean + name?: boolean +} + +export type TaskTagOmit = runtime.Types.Extensions.GetOmit<"id" | "uuid" | "taskUuid" | "taskId" | "name", ExtArgs["result"]["taskTag"]> +export type TaskTagInclude = { + task?: boolean | Prisma.TaskDefaultArgs +} +export type TaskTagIncludeCreateManyAndReturn = { + task?: boolean | Prisma.TaskDefaultArgs +} +export type TaskTagIncludeUpdateManyAndReturn = { + task?: boolean | Prisma.TaskDefaultArgs +} + +export type $TaskTagPayload = { + name: "TaskTag" + objects: { + task: Prisma.$TaskPayload + } + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: number + uuid: string + taskUuid: string | null + taskId: number + name: string + }, ExtArgs["result"]["taskTag"]> + composites: {} +} + +export type TaskTagGetPayload = runtime.Types.Result.GetResult + +export type TaskTagCountArgs = + Omit & { + select?: TaskTagCountAggregateInputType | true + } + +export interface TaskTagDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['TaskTag'], meta: { name: 'TaskTag' } } + /** + * Find zero or one TaskTag that matches the filter. + * @param {TaskTagFindUniqueArgs} args - Arguments to find a TaskTag + * @example + * // Get one TaskTag + * const taskTag = await prisma.taskTag.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__TaskTagClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one TaskTag that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {TaskTagFindUniqueOrThrowArgs} args - Arguments to find a TaskTag + * @example + * // Get one TaskTag + * const taskTag = await prisma.taskTag.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__TaskTagClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first TaskTag that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskTagFindFirstArgs} args - Arguments to find a TaskTag + * @example + * // Get one TaskTag + * const taskTag = await prisma.taskTag.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__TaskTagClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first TaskTag that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskTagFindFirstOrThrowArgs} args - Arguments to find a TaskTag + * @example + * // Get one TaskTag + * const taskTag = await prisma.taskTag.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__TaskTagClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more TaskTags that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskTagFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all TaskTags + * const taskTags = await prisma.taskTag.findMany() + * + * // Get first 10 TaskTags + * const taskTags = await prisma.taskTag.findMany({ take: 10 }) + * + * // Only select the `id` + * const taskTagWithIdOnly = await prisma.taskTag.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a TaskTag. + * @param {TaskTagCreateArgs} args - Arguments to create a TaskTag. + * @example + * // Create one TaskTag + * const TaskTag = await prisma.taskTag.create({ + * data: { + * // ... data to create a TaskTag + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__TaskTagClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many TaskTags. + * @param {TaskTagCreateManyArgs} args - Arguments to create many TaskTags. + * @example + * // Create many TaskTags + * const taskTag = await prisma.taskTag.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many TaskTags and returns the data saved in the database. + * @param {TaskTagCreateManyAndReturnArgs} args - Arguments to create many TaskTags. + * @example + * // Create many TaskTags + * const taskTag = await prisma.taskTag.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many TaskTags and only return the `id` + * const taskTagWithIdOnly = await prisma.taskTag.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a TaskTag. + * @param {TaskTagDeleteArgs} args - Arguments to delete one TaskTag. + * @example + * // Delete one TaskTag + * const TaskTag = await prisma.taskTag.delete({ + * where: { + * // ... filter to delete one TaskTag + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__TaskTagClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one TaskTag. + * @param {TaskTagUpdateArgs} args - Arguments to update one TaskTag. + * @example + * // Update one TaskTag + * const taskTag = await prisma.taskTag.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__TaskTagClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more TaskTags. + * @param {TaskTagDeleteManyArgs} args - Arguments to filter TaskTags to delete. + * @example + * // Delete a few TaskTags + * const { count } = await prisma.taskTag.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more TaskTags. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskTagUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many TaskTags + * const taskTag = await prisma.taskTag.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more TaskTags and returns the data updated in the database. + * @param {TaskTagUpdateManyAndReturnArgs} args - Arguments to update many TaskTags. + * @example + * // Update many TaskTags + * const taskTag = await prisma.taskTag.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more TaskTags and only return the `id` + * const taskTagWithIdOnly = await prisma.taskTag.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one TaskTag. + * @param {TaskTagUpsertArgs} args - Arguments to update or create a TaskTag. + * @example + * // Update or create a TaskTag + * const taskTag = await prisma.taskTag.upsert({ + * create: { + * // ... data to create a TaskTag + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the TaskTag we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__TaskTagClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of TaskTags. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskTagCountArgs} args - Arguments to filter TaskTags to count. + * @example + * // Count the number of TaskTags + * const count = await prisma.taskTag.count({ + * where: { + * // ... the filter for the TaskTags we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a TaskTag. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskTagAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by TaskTag. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {TaskTagGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends TaskTagGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: TaskTagGroupByArgs['orderBy'] } + : { orderBy?: TaskTagGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetTaskTagGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the TaskTag model + */ +readonly fields: TaskTagFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for TaskTag. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__TaskTagClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + task = {}>(args?: Prisma.Subset>): Prisma.Prisma__TaskClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the TaskTag model + */ +export interface TaskTagFieldRefs { + readonly id: Prisma.FieldRef<"TaskTag", 'Int'> + readonly uuid: Prisma.FieldRef<"TaskTag", 'String'> + readonly taskUuid: Prisma.FieldRef<"TaskTag", 'String'> + readonly taskId: Prisma.FieldRef<"TaskTag", 'Int'> + readonly name: Prisma.FieldRef<"TaskTag", 'String'> +} + + +// Custom InputTypes +/** + * TaskTag findUnique + */ +export type TaskTagFindUniqueArgs = { + /** + * Select specific fields to fetch from the TaskTag + */ + select?: Prisma.TaskTagSelect | null + /** + * Omit specific fields from the TaskTag + */ + omit?: Prisma.TaskTagOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskTagInclude | null + /** + * Filter, which TaskTag to fetch. + */ + where: Prisma.TaskTagWhereUniqueInput +} + +/** + * TaskTag findUniqueOrThrow + */ +export type TaskTagFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the TaskTag + */ + select?: Prisma.TaskTagSelect | null + /** + * Omit specific fields from the TaskTag + */ + omit?: Prisma.TaskTagOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskTagInclude | null + /** + * Filter, which TaskTag to fetch. + */ + where: Prisma.TaskTagWhereUniqueInput +} + +/** + * TaskTag findFirst + */ +export type TaskTagFindFirstArgs = { + /** + * Select specific fields to fetch from the TaskTag + */ + select?: Prisma.TaskTagSelect | null + /** + * Omit specific fields from the TaskTag + */ + omit?: Prisma.TaskTagOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskTagInclude | null + /** + * Filter, which TaskTag to fetch. + */ + where?: Prisma.TaskTagWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of TaskTags to fetch. + */ + orderBy?: Prisma.TaskTagOrderByWithRelationInput | Prisma.TaskTagOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for TaskTags. + */ + cursor?: Prisma.TaskTagWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` TaskTags from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` TaskTags. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of TaskTags. + */ + distinct?: Prisma.TaskTagScalarFieldEnum | Prisma.TaskTagScalarFieldEnum[] +} + +/** + * TaskTag findFirstOrThrow + */ +export type TaskTagFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the TaskTag + */ + select?: Prisma.TaskTagSelect | null + /** + * Omit specific fields from the TaskTag + */ + omit?: Prisma.TaskTagOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskTagInclude | null + /** + * Filter, which TaskTag to fetch. + */ + where?: Prisma.TaskTagWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of TaskTags to fetch. + */ + orderBy?: Prisma.TaskTagOrderByWithRelationInput | Prisma.TaskTagOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for TaskTags. + */ + cursor?: Prisma.TaskTagWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` TaskTags from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` TaskTags. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of TaskTags. + */ + distinct?: Prisma.TaskTagScalarFieldEnum | Prisma.TaskTagScalarFieldEnum[] +} + +/** + * TaskTag findMany + */ +export type TaskTagFindManyArgs = { + /** + * Select specific fields to fetch from the TaskTag + */ + select?: Prisma.TaskTagSelect | null + /** + * Omit specific fields from the TaskTag + */ + omit?: Prisma.TaskTagOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskTagInclude | null + /** + * Filter, which TaskTags to fetch. + */ + where?: Prisma.TaskTagWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of TaskTags to fetch. + */ + orderBy?: Prisma.TaskTagOrderByWithRelationInput | Prisma.TaskTagOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing TaskTags. + */ + cursor?: Prisma.TaskTagWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` TaskTags from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` TaskTags. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of TaskTags. + */ + distinct?: Prisma.TaskTagScalarFieldEnum | Prisma.TaskTagScalarFieldEnum[] +} + +/** + * TaskTag create + */ +export type TaskTagCreateArgs = { + /** + * Select specific fields to fetch from the TaskTag + */ + select?: Prisma.TaskTagSelect | null + /** + * Omit specific fields from the TaskTag + */ + omit?: Prisma.TaskTagOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskTagInclude | null + /** + * The data needed to create a TaskTag. + */ + data: Prisma.XOR +} + +/** + * TaskTag createMany + */ +export type TaskTagCreateManyArgs = { + /** + * The data used to create many TaskTags. + */ + data: Prisma.TaskTagCreateManyInput | Prisma.TaskTagCreateManyInput[] + skipDuplicates?: boolean +} + +/** + * TaskTag createManyAndReturn + */ +export type TaskTagCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the TaskTag + */ + select?: Prisma.TaskTagSelectCreateManyAndReturn | null + /** + * Omit specific fields from the TaskTag + */ + omit?: Prisma.TaskTagOmit | null + /** + * The data used to create many TaskTags. + */ + data: Prisma.TaskTagCreateManyInput | Prisma.TaskTagCreateManyInput[] + skipDuplicates?: boolean + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskTagIncludeCreateManyAndReturn | null +} + +/** + * TaskTag update + */ +export type TaskTagUpdateArgs = { + /** + * Select specific fields to fetch from the TaskTag + */ + select?: Prisma.TaskTagSelect | null + /** + * Omit specific fields from the TaskTag + */ + omit?: Prisma.TaskTagOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskTagInclude | null + /** + * The data needed to update a TaskTag. + */ + data: Prisma.XOR + /** + * Choose, which TaskTag to update. + */ + where: Prisma.TaskTagWhereUniqueInput +} + +/** + * TaskTag updateMany + */ +export type TaskTagUpdateManyArgs = { + /** + * The data used to update TaskTags. + */ + data: Prisma.XOR + /** + * Filter which TaskTags to update + */ + where?: Prisma.TaskTagWhereInput + /** + * Limit how many TaskTags to update. + */ + limit?: number +} + +/** + * TaskTag updateManyAndReturn + */ +export type TaskTagUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the TaskTag + */ + select?: Prisma.TaskTagSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the TaskTag + */ + omit?: Prisma.TaskTagOmit | null + /** + * The data used to update TaskTags. + */ + data: Prisma.XOR + /** + * Filter which TaskTags to update + */ + where?: Prisma.TaskTagWhereInput + /** + * Limit how many TaskTags to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskTagIncludeUpdateManyAndReturn | null +} + +/** + * TaskTag upsert + */ +export type TaskTagUpsertArgs = { + /** + * Select specific fields to fetch from the TaskTag + */ + select?: Prisma.TaskTagSelect | null + /** + * Omit specific fields from the TaskTag + */ + omit?: Prisma.TaskTagOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskTagInclude | null + /** + * The filter to search for the TaskTag to update in case it exists. + */ + where: Prisma.TaskTagWhereUniqueInput + /** + * In case the TaskTag found by the `where` argument doesn't exist, create a new TaskTag with this data. + */ + create: Prisma.XOR + /** + * In case the TaskTag was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * TaskTag delete + */ +export type TaskTagDeleteArgs = { + /** + * Select specific fields to fetch from the TaskTag + */ + select?: Prisma.TaskTagSelect | null + /** + * Omit specific fields from the TaskTag + */ + omit?: Prisma.TaskTagOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskTagInclude | null + /** + * Filter which TaskTag to delete. + */ + where: Prisma.TaskTagWhereUniqueInput +} + +/** + * TaskTag deleteMany + */ +export type TaskTagDeleteManyArgs = { + /** + * Filter which TaskTags to delete + */ + where?: Prisma.TaskTagWhereInput + /** + * Limit how many TaskTags to delete. + */ + limit?: number +} + +/** + * TaskTag without action + */ +export type TaskTagDefaultArgs = { + /** + * Select specific fields to fetch from the TaskTag + */ + select?: Prisma.TaskTagSelect | null + /** + * Omit specific fields from the TaskTag + */ + omit?: Prisma.TaskTagOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskTagInclude | null +} diff --git a/generated/prisma/models/User.ts b/generated/prisma/models/User.ts index de9031e..b3f6961 100644 --- a/generated/prisma/models/User.ts +++ b/generated/prisma/models/User.ts @@ -224,6 +224,15 @@ export type UserWhereInput = { password?: Prisma.StringFilter<"User"> | string createdAt?: Prisma.DateTimeFilter<"User"> | Date | string updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string + habits?: Prisma.HabitListRelationFilter + checkIns?: Prisma.CheckInListRelationFilter + checkLists?: Prisma.CheckListListRelationFilter + templates?: Prisma.CheckListTemplateListRelationFilter + ownedProjects?: Prisma.ProjectListRelationFilter + projectMembers?: Prisma.ProjectMemberListRelationFilter + assignedTasks?: Prisma.TaskListRelationFilter + createdTasks?: Prisma.TaskListRelationFilter + taskMembers?: Prisma.TaskMemberListRelationFilter } export type UserOrderByWithRelationInput = { @@ -233,7 +242,15 @@ export type UserOrderByWithRelationInput = { password?: Prisma.SortOrder createdAt?: Prisma.SortOrder updatedAt?: Prisma.SortOrder - _relevance?: Prisma.UserOrderByRelevanceInput + habits?: Prisma.HabitOrderByRelationAggregateInput + checkIns?: Prisma.CheckInOrderByRelationAggregateInput + checkLists?: Prisma.CheckListOrderByRelationAggregateInput + templates?: Prisma.CheckListTemplateOrderByRelationAggregateInput + ownedProjects?: Prisma.ProjectOrderByRelationAggregateInput + projectMembers?: Prisma.ProjectMemberOrderByRelationAggregateInput + assignedTasks?: Prisma.TaskOrderByRelationAggregateInput + createdTasks?: Prisma.TaskOrderByRelationAggregateInput + taskMembers?: Prisma.TaskMemberOrderByRelationAggregateInput } export type UserWhereUniqueInput = Prisma.AtLeast<{ @@ -246,6 +263,15 @@ export type UserWhereUniqueInput = Prisma.AtLeast<{ password?: Prisma.StringFilter<"User"> | string createdAt?: Prisma.DateTimeFilter<"User"> | Date | string updatedAt?: Prisma.DateTimeFilter<"User"> | Date | string + habits?: Prisma.HabitListRelationFilter + checkIns?: Prisma.CheckInListRelationFilter + checkLists?: Prisma.CheckListListRelationFilter + templates?: Prisma.CheckListTemplateListRelationFilter + ownedProjects?: Prisma.ProjectListRelationFilter + projectMembers?: Prisma.ProjectMemberListRelationFilter + assignedTasks?: Prisma.TaskListRelationFilter + createdTasks?: Prisma.TaskListRelationFilter + taskMembers?: Prisma.TaskMemberListRelationFilter }, "id" | "email"> export type UserOrderByWithAggregationInput = { @@ -280,6 +306,15 @@ export type UserCreateInput = { password: string createdAt?: Date | string updatedAt?: Date | string + habits?: Prisma.HabitCreateNestedManyWithoutUserInput + checkIns?: Prisma.CheckInCreateNestedManyWithoutUserInput + checkLists?: Prisma.CheckListCreateNestedManyWithoutUserInput + templates?: Prisma.CheckListTemplateCreateNestedManyWithoutUserInput + ownedProjects?: Prisma.ProjectCreateNestedManyWithoutOwnerInput + projectMembers?: Prisma.ProjectMemberCreateNestedManyWithoutUserInput + assignedTasks?: Prisma.TaskCreateNestedManyWithoutAssigneeInput + createdTasks?: Prisma.TaskCreateNestedManyWithoutCreatorInput + taskMembers?: Prisma.TaskMemberCreateNestedManyWithoutUserInput } export type UserUncheckedCreateInput = { @@ -289,6 +324,15 @@ export type UserUncheckedCreateInput = { password: string createdAt?: Date | string updatedAt?: Date | string + habits?: Prisma.HabitUncheckedCreateNestedManyWithoutUserInput + checkIns?: Prisma.CheckInUncheckedCreateNestedManyWithoutUserInput + checkLists?: Prisma.CheckListUncheckedCreateNestedManyWithoutUserInput + templates?: Prisma.CheckListTemplateUncheckedCreateNestedManyWithoutUserInput + ownedProjects?: Prisma.ProjectUncheckedCreateNestedManyWithoutOwnerInput + projectMembers?: Prisma.ProjectMemberUncheckedCreateNestedManyWithoutUserInput + assignedTasks?: Prisma.TaskUncheckedCreateNestedManyWithoutAssigneeInput + createdTasks?: Prisma.TaskUncheckedCreateNestedManyWithoutCreatorInput + taskMembers?: Prisma.TaskMemberUncheckedCreateNestedManyWithoutUserInput } export type UserUpdateInput = { @@ -297,6 +341,15 @@ export type UserUpdateInput = { password?: Prisma.StringFieldUpdateOperationsInput | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habits?: Prisma.HabitUpdateManyWithoutUserNestedInput + checkIns?: Prisma.CheckInUpdateManyWithoutUserNestedInput + checkLists?: Prisma.CheckListUpdateManyWithoutUserNestedInput + templates?: Prisma.CheckListTemplateUpdateManyWithoutUserNestedInput + ownedProjects?: Prisma.ProjectUpdateManyWithoutOwnerNestedInput + projectMembers?: Prisma.ProjectMemberUpdateManyWithoutUserNestedInput + assignedTasks?: Prisma.TaskUpdateManyWithoutAssigneeNestedInput + createdTasks?: Prisma.TaskUpdateManyWithoutCreatorNestedInput + taskMembers?: Prisma.TaskMemberUpdateManyWithoutUserNestedInput } export type UserUncheckedUpdateInput = { @@ -306,6 +359,15 @@ export type UserUncheckedUpdateInput = { password?: Prisma.StringFieldUpdateOperationsInput | string createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habits?: Prisma.HabitUncheckedUpdateManyWithoutUserNestedInput + checkIns?: Prisma.CheckInUncheckedUpdateManyWithoutUserNestedInput + checkLists?: Prisma.CheckListUncheckedUpdateManyWithoutUserNestedInput + templates?: Prisma.CheckListTemplateUncheckedUpdateManyWithoutUserNestedInput + ownedProjects?: Prisma.ProjectUncheckedUpdateManyWithoutOwnerNestedInput + projectMembers?: Prisma.ProjectMemberUncheckedUpdateManyWithoutUserNestedInput + assignedTasks?: Prisma.TaskUncheckedUpdateManyWithoutAssigneeNestedInput + createdTasks?: Prisma.TaskUncheckedUpdateManyWithoutCreatorNestedInput + taskMembers?: Prisma.TaskMemberUncheckedUpdateManyWithoutUserNestedInput } export type UserCreateManyInput = { @@ -334,12 +396,6 @@ export type UserUncheckedUpdateManyInput = { updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string } -export type UserOrderByRelevanceInput = { - fields: Prisma.UserOrderByRelevanceFieldEnum | Prisma.UserOrderByRelevanceFieldEnum[] - sort: Prisma.SortOrder - search: string -} - export type UserCountOrderByAggregateInput = { id?: Prisma.SortOrder name?: Prisma.SortOrder @@ -375,6 +431,16 @@ export type UserSumOrderByAggregateInput = { id?: Prisma.SortOrder } +export type UserScalarRelationFilter = { + is?: Prisma.UserWhereInput + isNot?: Prisma.UserWhereInput +} + +export type UserNullableScalarRelationFilter = { + is?: Prisma.UserWhereInput | null + isNot?: Prisma.UserWhereInput | null +} + export type StringFieldUpdateOperationsInput = { set?: string } @@ -391,6 +457,975 @@ export type IntFieldUpdateOperationsInput = { divide?: number } +export type UserCreateNestedOneWithoutHabitsInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.UserCreateOrConnectWithoutHabitsInput + connect?: Prisma.UserWhereUniqueInput +} + +export type UserUpdateOneRequiredWithoutHabitsNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.UserCreateOrConnectWithoutHabitsInput + upsert?: Prisma.UserUpsertWithoutHabitsInput + connect?: Prisma.UserWhereUniqueInput + update?: Prisma.XOR, Prisma.UserUncheckedUpdateWithoutHabitsInput> +} + +export type UserCreateNestedOneWithoutCheckInsInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.UserCreateOrConnectWithoutCheckInsInput + connect?: Prisma.UserWhereUniqueInput +} + +export type UserUpdateOneRequiredWithoutCheckInsNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.UserCreateOrConnectWithoutCheckInsInput + upsert?: Prisma.UserUpsertWithoutCheckInsInput + connect?: Prisma.UserWhereUniqueInput + update?: Prisma.XOR, Prisma.UserUncheckedUpdateWithoutCheckInsInput> +} + +export type UserCreateNestedOneWithoutCheckListsInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.UserCreateOrConnectWithoutCheckListsInput + connect?: Prisma.UserWhereUniqueInput +} + +export type UserUpdateOneRequiredWithoutCheckListsNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.UserCreateOrConnectWithoutCheckListsInput + upsert?: Prisma.UserUpsertWithoutCheckListsInput + connect?: Prisma.UserWhereUniqueInput + update?: Prisma.XOR, Prisma.UserUncheckedUpdateWithoutCheckListsInput> +} + +export type UserCreateNestedOneWithoutTemplatesInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.UserCreateOrConnectWithoutTemplatesInput + connect?: Prisma.UserWhereUniqueInput +} + +export type UserUpdateOneWithoutTemplatesNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.UserCreateOrConnectWithoutTemplatesInput + upsert?: Prisma.UserUpsertWithoutTemplatesInput + disconnect?: Prisma.UserWhereInput | boolean + delete?: Prisma.UserWhereInput | boolean + connect?: Prisma.UserWhereUniqueInput + update?: Prisma.XOR, Prisma.UserUncheckedUpdateWithoutTemplatesInput> +} + +export type UserCreateNestedOneWithoutOwnedProjectsInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.UserCreateOrConnectWithoutOwnedProjectsInput + connect?: Prisma.UserWhereUniqueInput +} + +export type UserUpdateOneRequiredWithoutOwnedProjectsNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.UserCreateOrConnectWithoutOwnedProjectsInput + upsert?: Prisma.UserUpsertWithoutOwnedProjectsInput + connect?: Prisma.UserWhereUniqueInput + update?: Prisma.XOR, Prisma.UserUncheckedUpdateWithoutOwnedProjectsInput> +} + +export type UserCreateNestedOneWithoutProjectMembersInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.UserCreateOrConnectWithoutProjectMembersInput + connect?: Prisma.UserWhereUniqueInput +} + +export type UserUpdateOneRequiredWithoutProjectMembersNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.UserCreateOrConnectWithoutProjectMembersInput + upsert?: Prisma.UserUpsertWithoutProjectMembersInput + connect?: Prisma.UserWhereUniqueInput + update?: Prisma.XOR, Prisma.UserUncheckedUpdateWithoutProjectMembersInput> +} + +export type UserCreateNestedOneWithoutAssignedTasksInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.UserCreateOrConnectWithoutAssignedTasksInput + connect?: Prisma.UserWhereUniqueInput +} + +export type UserCreateNestedOneWithoutCreatedTasksInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.UserCreateOrConnectWithoutCreatedTasksInput + connect?: Prisma.UserWhereUniqueInput +} + +export type UserUpdateOneWithoutAssignedTasksNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.UserCreateOrConnectWithoutAssignedTasksInput + upsert?: Prisma.UserUpsertWithoutAssignedTasksInput + disconnect?: Prisma.UserWhereInput | boolean + delete?: Prisma.UserWhereInput | boolean + connect?: Prisma.UserWhereUniqueInput + update?: Prisma.XOR, Prisma.UserUncheckedUpdateWithoutAssignedTasksInput> +} + +export type UserUpdateOneRequiredWithoutCreatedTasksNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.UserCreateOrConnectWithoutCreatedTasksInput + upsert?: Prisma.UserUpsertWithoutCreatedTasksInput + connect?: Prisma.UserWhereUniqueInput + update?: Prisma.XOR, Prisma.UserUncheckedUpdateWithoutCreatedTasksInput> +} + +export type UserCreateNestedOneWithoutTaskMembersInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.UserCreateOrConnectWithoutTaskMembersInput + connect?: Prisma.UserWhereUniqueInput +} + +export type UserUpdateOneRequiredWithoutTaskMembersNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.UserCreateOrConnectWithoutTaskMembersInput + upsert?: Prisma.UserUpsertWithoutTaskMembersInput + connect?: Prisma.UserWhereUniqueInput + update?: Prisma.XOR, Prisma.UserUncheckedUpdateWithoutTaskMembersInput> +} + +export type UserCreateWithoutHabitsInput = { + name: string + email: string + password: string + createdAt?: Date | string + updatedAt?: Date | string + checkIns?: Prisma.CheckInCreateNestedManyWithoutUserInput + checkLists?: Prisma.CheckListCreateNestedManyWithoutUserInput + templates?: Prisma.CheckListTemplateCreateNestedManyWithoutUserInput + ownedProjects?: Prisma.ProjectCreateNestedManyWithoutOwnerInput + projectMembers?: Prisma.ProjectMemberCreateNestedManyWithoutUserInput + assignedTasks?: Prisma.TaskCreateNestedManyWithoutAssigneeInput + createdTasks?: Prisma.TaskCreateNestedManyWithoutCreatorInput + taskMembers?: Prisma.TaskMemberCreateNestedManyWithoutUserInput +} + +export type UserUncheckedCreateWithoutHabitsInput = { + id?: number + name: string + email: string + password: string + createdAt?: Date | string + updatedAt?: Date | string + checkIns?: Prisma.CheckInUncheckedCreateNestedManyWithoutUserInput + checkLists?: Prisma.CheckListUncheckedCreateNestedManyWithoutUserInput + templates?: Prisma.CheckListTemplateUncheckedCreateNestedManyWithoutUserInput + ownedProjects?: Prisma.ProjectUncheckedCreateNestedManyWithoutOwnerInput + projectMembers?: Prisma.ProjectMemberUncheckedCreateNestedManyWithoutUserInput + assignedTasks?: Prisma.TaskUncheckedCreateNestedManyWithoutAssigneeInput + createdTasks?: Prisma.TaskUncheckedCreateNestedManyWithoutCreatorInput + taskMembers?: Prisma.TaskMemberUncheckedCreateNestedManyWithoutUserInput +} + +export type UserCreateOrConnectWithoutHabitsInput = { + where: Prisma.UserWhereUniqueInput + create: Prisma.XOR +} + +export type UserUpsertWithoutHabitsInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.UserWhereInput +} + +export type UserUpdateToOneWithWhereWithoutHabitsInput = { + where?: Prisma.UserWhereInput + data: Prisma.XOR +} + +export type UserUpdateWithoutHabitsInput = { + name?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + checkIns?: Prisma.CheckInUpdateManyWithoutUserNestedInput + checkLists?: Prisma.CheckListUpdateManyWithoutUserNestedInput + templates?: Prisma.CheckListTemplateUpdateManyWithoutUserNestedInput + ownedProjects?: Prisma.ProjectUpdateManyWithoutOwnerNestedInput + projectMembers?: Prisma.ProjectMemberUpdateManyWithoutUserNestedInput + assignedTasks?: Prisma.TaskUpdateManyWithoutAssigneeNestedInput + createdTasks?: Prisma.TaskUpdateManyWithoutCreatorNestedInput + taskMembers?: Prisma.TaskMemberUpdateManyWithoutUserNestedInput +} + +export type UserUncheckedUpdateWithoutHabitsInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + checkIns?: Prisma.CheckInUncheckedUpdateManyWithoutUserNestedInput + checkLists?: Prisma.CheckListUncheckedUpdateManyWithoutUserNestedInput + templates?: Prisma.CheckListTemplateUncheckedUpdateManyWithoutUserNestedInput + ownedProjects?: Prisma.ProjectUncheckedUpdateManyWithoutOwnerNestedInput + projectMembers?: Prisma.ProjectMemberUncheckedUpdateManyWithoutUserNestedInput + assignedTasks?: Prisma.TaskUncheckedUpdateManyWithoutAssigneeNestedInput + createdTasks?: Prisma.TaskUncheckedUpdateManyWithoutCreatorNestedInput + taskMembers?: Prisma.TaskMemberUncheckedUpdateManyWithoutUserNestedInput +} + +export type UserCreateWithoutCheckInsInput = { + name: string + email: string + password: string + createdAt?: Date | string + updatedAt?: Date | string + habits?: Prisma.HabitCreateNestedManyWithoutUserInput + checkLists?: Prisma.CheckListCreateNestedManyWithoutUserInput + templates?: Prisma.CheckListTemplateCreateNestedManyWithoutUserInput + ownedProjects?: Prisma.ProjectCreateNestedManyWithoutOwnerInput + projectMembers?: Prisma.ProjectMemberCreateNestedManyWithoutUserInput + assignedTasks?: Prisma.TaskCreateNestedManyWithoutAssigneeInput + createdTasks?: Prisma.TaskCreateNestedManyWithoutCreatorInput + taskMembers?: Prisma.TaskMemberCreateNestedManyWithoutUserInput +} + +export type UserUncheckedCreateWithoutCheckInsInput = { + id?: number + name: string + email: string + password: string + createdAt?: Date | string + updatedAt?: Date | string + habits?: Prisma.HabitUncheckedCreateNestedManyWithoutUserInput + checkLists?: Prisma.CheckListUncheckedCreateNestedManyWithoutUserInput + templates?: Prisma.CheckListTemplateUncheckedCreateNestedManyWithoutUserInput + ownedProjects?: Prisma.ProjectUncheckedCreateNestedManyWithoutOwnerInput + projectMembers?: Prisma.ProjectMemberUncheckedCreateNestedManyWithoutUserInput + assignedTasks?: Prisma.TaskUncheckedCreateNestedManyWithoutAssigneeInput + createdTasks?: Prisma.TaskUncheckedCreateNestedManyWithoutCreatorInput + taskMembers?: Prisma.TaskMemberUncheckedCreateNestedManyWithoutUserInput +} + +export type UserCreateOrConnectWithoutCheckInsInput = { + where: Prisma.UserWhereUniqueInput + create: Prisma.XOR +} + +export type UserUpsertWithoutCheckInsInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.UserWhereInput +} + +export type UserUpdateToOneWithWhereWithoutCheckInsInput = { + where?: Prisma.UserWhereInput + data: Prisma.XOR +} + +export type UserUpdateWithoutCheckInsInput = { + name?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habits?: Prisma.HabitUpdateManyWithoutUserNestedInput + checkLists?: Prisma.CheckListUpdateManyWithoutUserNestedInput + templates?: Prisma.CheckListTemplateUpdateManyWithoutUserNestedInput + ownedProjects?: Prisma.ProjectUpdateManyWithoutOwnerNestedInput + projectMembers?: Prisma.ProjectMemberUpdateManyWithoutUserNestedInput + assignedTasks?: Prisma.TaskUpdateManyWithoutAssigneeNestedInput + createdTasks?: Prisma.TaskUpdateManyWithoutCreatorNestedInput + taskMembers?: Prisma.TaskMemberUpdateManyWithoutUserNestedInput +} + +export type UserUncheckedUpdateWithoutCheckInsInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habits?: Prisma.HabitUncheckedUpdateManyWithoutUserNestedInput + checkLists?: Prisma.CheckListUncheckedUpdateManyWithoutUserNestedInput + templates?: Prisma.CheckListTemplateUncheckedUpdateManyWithoutUserNestedInput + ownedProjects?: Prisma.ProjectUncheckedUpdateManyWithoutOwnerNestedInput + projectMembers?: Prisma.ProjectMemberUncheckedUpdateManyWithoutUserNestedInput + assignedTasks?: Prisma.TaskUncheckedUpdateManyWithoutAssigneeNestedInput + createdTasks?: Prisma.TaskUncheckedUpdateManyWithoutCreatorNestedInput + taskMembers?: Prisma.TaskMemberUncheckedUpdateManyWithoutUserNestedInput +} + +export type UserCreateWithoutCheckListsInput = { + name: string + email: string + password: string + createdAt?: Date | string + updatedAt?: Date | string + habits?: Prisma.HabitCreateNestedManyWithoutUserInput + checkIns?: Prisma.CheckInCreateNestedManyWithoutUserInput + templates?: Prisma.CheckListTemplateCreateNestedManyWithoutUserInput + ownedProjects?: Prisma.ProjectCreateNestedManyWithoutOwnerInput + projectMembers?: Prisma.ProjectMemberCreateNestedManyWithoutUserInput + assignedTasks?: Prisma.TaskCreateNestedManyWithoutAssigneeInput + createdTasks?: Prisma.TaskCreateNestedManyWithoutCreatorInput + taskMembers?: Prisma.TaskMemberCreateNestedManyWithoutUserInput +} + +export type UserUncheckedCreateWithoutCheckListsInput = { + id?: number + name: string + email: string + password: string + createdAt?: Date | string + updatedAt?: Date | string + habits?: Prisma.HabitUncheckedCreateNestedManyWithoutUserInput + checkIns?: Prisma.CheckInUncheckedCreateNestedManyWithoutUserInput + templates?: Prisma.CheckListTemplateUncheckedCreateNestedManyWithoutUserInput + ownedProjects?: Prisma.ProjectUncheckedCreateNestedManyWithoutOwnerInput + projectMembers?: Prisma.ProjectMemberUncheckedCreateNestedManyWithoutUserInput + assignedTasks?: Prisma.TaskUncheckedCreateNestedManyWithoutAssigneeInput + createdTasks?: Prisma.TaskUncheckedCreateNestedManyWithoutCreatorInput + taskMembers?: Prisma.TaskMemberUncheckedCreateNestedManyWithoutUserInput +} + +export type UserCreateOrConnectWithoutCheckListsInput = { + where: Prisma.UserWhereUniqueInput + create: Prisma.XOR +} + +export type UserUpsertWithoutCheckListsInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.UserWhereInput +} + +export type UserUpdateToOneWithWhereWithoutCheckListsInput = { + where?: Prisma.UserWhereInput + data: Prisma.XOR +} + +export type UserUpdateWithoutCheckListsInput = { + name?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habits?: Prisma.HabitUpdateManyWithoutUserNestedInput + checkIns?: Prisma.CheckInUpdateManyWithoutUserNestedInput + templates?: Prisma.CheckListTemplateUpdateManyWithoutUserNestedInput + ownedProjects?: Prisma.ProjectUpdateManyWithoutOwnerNestedInput + projectMembers?: Prisma.ProjectMemberUpdateManyWithoutUserNestedInput + assignedTasks?: Prisma.TaskUpdateManyWithoutAssigneeNestedInput + createdTasks?: Prisma.TaskUpdateManyWithoutCreatorNestedInput + taskMembers?: Prisma.TaskMemberUpdateManyWithoutUserNestedInput +} + +export type UserUncheckedUpdateWithoutCheckListsInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habits?: Prisma.HabitUncheckedUpdateManyWithoutUserNestedInput + checkIns?: Prisma.CheckInUncheckedUpdateManyWithoutUserNestedInput + templates?: Prisma.CheckListTemplateUncheckedUpdateManyWithoutUserNestedInput + ownedProjects?: Prisma.ProjectUncheckedUpdateManyWithoutOwnerNestedInput + projectMembers?: Prisma.ProjectMemberUncheckedUpdateManyWithoutUserNestedInput + assignedTasks?: Prisma.TaskUncheckedUpdateManyWithoutAssigneeNestedInput + createdTasks?: Prisma.TaskUncheckedUpdateManyWithoutCreatorNestedInput + taskMembers?: Prisma.TaskMemberUncheckedUpdateManyWithoutUserNestedInput +} + +export type UserCreateWithoutTemplatesInput = { + name: string + email: string + password: string + createdAt?: Date | string + updatedAt?: Date | string + habits?: Prisma.HabitCreateNestedManyWithoutUserInput + checkIns?: Prisma.CheckInCreateNestedManyWithoutUserInput + checkLists?: Prisma.CheckListCreateNestedManyWithoutUserInput + ownedProjects?: Prisma.ProjectCreateNestedManyWithoutOwnerInput + projectMembers?: Prisma.ProjectMemberCreateNestedManyWithoutUserInput + assignedTasks?: Prisma.TaskCreateNestedManyWithoutAssigneeInput + createdTasks?: Prisma.TaskCreateNestedManyWithoutCreatorInput + taskMembers?: Prisma.TaskMemberCreateNestedManyWithoutUserInput +} + +export type UserUncheckedCreateWithoutTemplatesInput = { + id?: number + name: string + email: string + password: string + createdAt?: Date | string + updatedAt?: Date | string + habits?: Prisma.HabitUncheckedCreateNestedManyWithoutUserInput + checkIns?: Prisma.CheckInUncheckedCreateNestedManyWithoutUserInput + checkLists?: Prisma.CheckListUncheckedCreateNestedManyWithoutUserInput + ownedProjects?: Prisma.ProjectUncheckedCreateNestedManyWithoutOwnerInput + projectMembers?: Prisma.ProjectMemberUncheckedCreateNestedManyWithoutUserInput + assignedTasks?: Prisma.TaskUncheckedCreateNestedManyWithoutAssigneeInput + createdTasks?: Prisma.TaskUncheckedCreateNestedManyWithoutCreatorInput + taskMembers?: Prisma.TaskMemberUncheckedCreateNestedManyWithoutUserInput +} + +export type UserCreateOrConnectWithoutTemplatesInput = { + where: Prisma.UserWhereUniqueInput + create: Prisma.XOR +} + +export type UserUpsertWithoutTemplatesInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.UserWhereInput +} + +export type UserUpdateToOneWithWhereWithoutTemplatesInput = { + where?: Prisma.UserWhereInput + data: Prisma.XOR +} + +export type UserUpdateWithoutTemplatesInput = { + name?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habits?: Prisma.HabitUpdateManyWithoutUserNestedInput + checkIns?: Prisma.CheckInUpdateManyWithoutUserNestedInput + checkLists?: Prisma.CheckListUpdateManyWithoutUserNestedInput + ownedProjects?: Prisma.ProjectUpdateManyWithoutOwnerNestedInput + projectMembers?: Prisma.ProjectMemberUpdateManyWithoutUserNestedInput + assignedTasks?: Prisma.TaskUpdateManyWithoutAssigneeNestedInput + createdTasks?: Prisma.TaskUpdateManyWithoutCreatorNestedInput + taskMembers?: Prisma.TaskMemberUpdateManyWithoutUserNestedInput +} + +export type UserUncheckedUpdateWithoutTemplatesInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habits?: Prisma.HabitUncheckedUpdateManyWithoutUserNestedInput + checkIns?: Prisma.CheckInUncheckedUpdateManyWithoutUserNestedInput + checkLists?: Prisma.CheckListUncheckedUpdateManyWithoutUserNestedInput + ownedProjects?: Prisma.ProjectUncheckedUpdateManyWithoutOwnerNestedInput + projectMembers?: Prisma.ProjectMemberUncheckedUpdateManyWithoutUserNestedInput + assignedTasks?: Prisma.TaskUncheckedUpdateManyWithoutAssigneeNestedInput + createdTasks?: Prisma.TaskUncheckedUpdateManyWithoutCreatorNestedInput + taskMembers?: Prisma.TaskMemberUncheckedUpdateManyWithoutUserNestedInput +} + +export type UserCreateWithoutOwnedProjectsInput = { + name: string + email: string + password: string + createdAt?: Date | string + updatedAt?: Date | string + habits?: Prisma.HabitCreateNestedManyWithoutUserInput + checkIns?: Prisma.CheckInCreateNestedManyWithoutUserInput + checkLists?: Prisma.CheckListCreateNestedManyWithoutUserInput + templates?: Prisma.CheckListTemplateCreateNestedManyWithoutUserInput + projectMembers?: Prisma.ProjectMemberCreateNestedManyWithoutUserInput + assignedTasks?: Prisma.TaskCreateNestedManyWithoutAssigneeInput + createdTasks?: Prisma.TaskCreateNestedManyWithoutCreatorInput + taskMembers?: Prisma.TaskMemberCreateNestedManyWithoutUserInput +} + +export type UserUncheckedCreateWithoutOwnedProjectsInput = { + id?: number + name: string + email: string + password: string + createdAt?: Date | string + updatedAt?: Date | string + habits?: Prisma.HabitUncheckedCreateNestedManyWithoutUserInput + checkIns?: Prisma.CheckInUncheckedCreateNestedManyWithoutUserInput + checkLists?: Prisma.CheckListUncheckedCreateNestedManyWithoutUserInput + templates?: Prisma.CheckListTemplateUncheckedCreateNestedManyWithoutUserInput + projectMembers?: Prisma.ProjectMemberUncheckedCreateNestedManyWithoutUserInput + assignedTasks?: Prisma.TaskUncheckedCreateNestedManyWithoutAssigneeInput + createdTasks?: Prisma.TaskUncheckedCreateNestedManyWithoutCreatorInput + taskMembers?: Prisma.TaskMemberUncheckedCreateNestedManyWithoutUserInput +} + +export type UserCreateOrConnectWithoutOwnedProjectsInput = { + where: Prisma.UserWhereUniqueInput + create: Prisma.XOR +} + +export type UserUpsertWithoutOwnedProjectsInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.UserWhereInput +} + +export type UserUpdateToOneWithWhereWithoutOwnedProjectsInput = { + where?: Prisma.UserWhereInput + data: Prisma.XOR +} + +export type UserUpdateWithoutOwnedProjectsInput = { + name?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habits?: Prisma.HabitUpdateManyWithoutUserNestedInput + checkIns?: Prisma.CheckInUpdateManyWithoutUserNestedInput + checkLists?: Prisma.CheckListUpdateManyWithoutUserNestedInput + templates?: Prisma.CheckListTemplateUpdateManyWithoutUserNestedInput + projectMembers?: Prisma.ProjectMemberUpdateManyWithoutUserNestedInput + assignedTasks?: Prisma.TaskUpdateManyWithoutAssigneeNestedInput + createdTasks?: Prisma.TaskUpdateManyWithoutCreatorNestedInput + taskMembers?: Prisma.TaskMemberUpdateManyWithoutUserNestedInput +} + +export type UserUncheckedUpdateWithoutOwnedProjectsInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habits?: Prisma.HabitUncheckedUpdateManyWithoutUserNestedInput + checkIns?: Prisma.CheckInUncheckedUpdateManyWithoutUserNestedInput + checkLists?: Prisma.CheckListUncheckedUpdateManyWithoutUserNestedInput + templates?: Prisma.CheckListTemplateUncheckedUpdateManyWithoutUserNestedInput + projectMembers?: Prisma.ProjectMemberUncheckedUpdateManyWithoutUserNestedInput + assignedTasks?: Prisma.TaskUncheckedUpdateManyWithoutAssigneeNestedInput + createdTasks?: Prisma.TaskUncheckedUpdateManyWithoutCreatorNestedInput + taskMembers?: Prisma.TaskMemberUncheckedUpdateManyWithoutUserNestedInput +} + +export type UserCreateWithoutProjectMembersInput = { + name: string + email: string + password: string + createdAt?: Date | string + updatedAt?: Date | string + habits?: Prisma.HabitCreateNestedManyWithoutUserInput + checkIns?: Prisma.CheckInCreateNestedManyWithoutUserInput + checkLists?: Prisma.CheckListCreateNestedManyWithoutUserInput + templates?: Prisma.CheckListTemplateCreateNestedManyWithoutUserInput + ownedProjects?: Prisma.ProjectCreateNestedManyWithoutOwnerInput + assignedTasks?: Prisma.TaskCreateNestedManyWithoutAssigneeInput + createdTasks?: Prisma.TaskCreateNestedManyWithoutCreatorInput + taskMembers?: Prisma.TaskMemberCreateNestedManyWithoutUserInput +} + +export type UserUncheckedCreateWithoutProjectMembersInput = { + id?: number + name: string + email: string + password: string + createdAt?: Date | string + updatedAt?: Date | string + habits?: Prisma.HabitUncheckedCreateNestedManyWithoutUserInput + checkIns?: Prisma.CheckInUncheckedCreateNestedManyWithoutUserInput + checkLists?: Prisma.CheckListUncheckedCreateNestedManyWithoutUserInput + templates?: Prisma.CheckListTemplateUncheckedCreateNestedManyWithoutUserInput + ownedProjects?: Prisma.ProjectUncheckedCreateNestedManyWithoutOwnerInput + assignedTasks?: Prisma.TaskUncheckedCreateNestedManyWithoutAssigneeInput + createdTasks?: Prisma.TaskUncheckedCreateNestedManyWithoutCreatorInput + taskMembers?: Prisma.TaskMemberUncheckedCreateNestedManyWithoutUserInput +} + +export type UserCreateOrConnectWithoutProjectMembersInput = { + where: Prisma.UserWhereUniqueInput + create: Prisma.XOR +} + +export type UserUpsertWithoutProjectMembersInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.UserWhereInput +} + +export type UserUpdateToOneWithWhereWithoutProjectMembersInput = { + where?: Prisma.UserWhereInput + data: Prisma.XOR +} + +export type UserUpdateWithoutProjectMembersInput = { + name?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habits?: Prisma.HabitUpdateManyWithoutUserNestedInput + checkIns?: Prisma.CheckInUpdateManyWithoutUserNestedInput + checkLists?: Prisma.CheckListUpdateManyWithoutUserNestedInput + templates?: Prisma.CheckListTemplateUpdateManyWithoutUserNestedInput + ownedProjects?: Prisma.ProjectUpdateManyWithoutOwnerNestedInput + assignedTasks?: Prisma.TaskUpdateManyWithoutAssigneeNestedInput + createdTasks?: Prisma.TaskUpdateManyWithoutCreatorNestedInput + taskMembers?: Prisma.TaskMemberUpdateManyWithoutUserNestedInput +} + +export type UserUncheckedUpdateWithoutProjectMembersInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habits?: Prisma.HabitUncheckedUpdateManyWithoutUserNestedInput + checkIns?: Prisma.CheckInUncheckedUpdateManyWithoutUserNestedInput + checkLists?: Prisma.CheckListUncheckedUpdateManyWithoutUserNestedInput + templates?: Prisma.CheckListTemplateUncheckedUpdateManyWithoutUserNestedInput + ownedProjects?: Prisma.ProjectUncheckedUpdateManyWithoutOwnerNestedInput + assignedTasks?: Prisma.TaskUncheckedUpdateManyWithoutAssigneeNestedInput + createdTasks?: Prisma.TaskUncheckedUpdateManyWithoutCreatorNestedInput + taskMembers?: Prisma.TaskMemberUncheckedUpdateManyWithoutUserNestedInput +} + +export type UserCreateWithoutAssignedTasksInput = { + name: string + email: string + password: string + createdAt?: Date | string + updatedAt?: Date | string + habits?: Prisma.HabitCreateNestedManyWithoutUserInput + checkIns?: Prisma.CheckInCreateNestedManyWithoutUserInput + checkLists?: Prisma.CheckListCreateNestedManyWithoutUserInput + templates?: Prisma.CheckListTemplateCreateNestedManyWithoutUserInput + ownedProjects?: Prisma.ProjectCreateNestedManyWithoutOwnerInput + projectMembers?: Prisma.ProjectMemberCreateNestedManyWithoutUserInput + createdTasks?: Prisma.TaskCreateNestedManyWithoutCreatorInput + taskMembers?: Prisma.TaskMemberCreateNestedManyWithoutUserInput +} + +export type UserUncheckedCreateWithoutAssignedTasksInput = { + id?: number + name: string + email: string + password: string + createdAt?: Date | string + updatedAt?: Date | string + habits?: Prisma.HabitUncheckedCreateNestedManyWithoutUserInput + checkIns?: Prisma.CheckInUncheckedCreateNestedManyWithoutUserInput + checkLists?: Prisma.CheckListUncheckedCreateNestedManyWithoutUserInput + templates?: Prisma.CheckListTemplateUncheckedCreateNestedManyWithoutUserInput + ownedProjects?: Prisma.ProjectUncheckedCreateNestedManyWithoutOwnerInput + projectMembers?: Prisma.ProjectMemberUncheckedCreateNestedManyWithoutUserInput + createdTasks?: Prisma.TaskUncheckedCreateNestedManyWithoutCreatorInput + taskMembers?: Prisma.TaskMemberUncheckedCreateNestedManyWithoutUserInput +} + +export type UserCreateOrConnectWithoutAssignedTasksInput = { + where: Prisma.UserWhereUniqueInput + create: Prisma.XOR +} + +export type UserCreateWithoutCreatedTasksInput = { + name: string + email: string + password: string + createdAt?: Date | string + updatedAt?: Date | string + habits?: Prisma.HabitCreateNestedManyWithoutUserInput + checkIns?: Prisma.CheckInCreateNestedManyWithoutUserInput + checkLists?: Prisma.CheckListCreateNestedManyWithoutUserInput + templates?: Prisma.CheckListTemplateCreateNestedManyWithoutUserInput + ownedProjects?: Prisma.ProjectCreateNestedManyWithoutOwnerInput + projectMembers?: Prisma.ProjectMemberCreateNestedManyWithoutUserInput + assignedTasks?: Prisma.TaskCreateNestedManyWithoutAssigneeInput + taskMembers?: Prisma.TaskMemberCreateNestedManyWithoutUserInput +} + +export type UserUncheckedCreateWithoutCreatedTasksInput = { + id?: number + name: string + email: string + password: string + createdAt?: Date | string + updatedAt?: Date | string + habits?: Prisma.HabitUncheckedCreateNestedManyWithoutUserInput + checkIns?: Prisma.CheckInUncheckedCreateNestedManyWithoutUserInput + checkLists?: Prisma.CheckListUncheckedCreateNestedManyWithoutUserInput + templates?: Prisma.CheckListTemplateUncheckedCreateNestedManyWithoutUserInput + ownedProjects?: Prisma.ProjectUncheckedCreateNestedManyWithoutOwnerInput + projectMembers?: Prisma.ProjectMemberUncheckedCreateNestedManyWithoutUserInput + assignedTasks?: Prisma.TaskUncheckedCreateNestedManyWithoutAssigneeInput + taskMembers?: Prisma.TaskMemberUncheckedCreateNestedManyWithoutUserInput +} + +export type UserCreateOrConnectWithoutCreatedTasksInput = { + where: Prisma.UserWhereUniqueInput + create: Prisma.XOR +} + +export type UserUpsertWithoutAssignedTasksInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.UserWhereInput +} + +export type UserUpdateToOneWithWhereWithoutAssignedTasksInput = { + where?: Prisma.UserWhereInput + data: Prisma.XOR +} + +export type UserUpdateWithoutAssignedTasksInput = { + name?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habits?: Prisma.HabitUpdateManyWithoutUserNestedInput + checkIns?: Prisma.CheckInUpdateManyWithoutUserNestedInput + checkLists?: Prisma.CheckListUpdateManyWithoutUserNestedInput + templates?: Prisma.CheckListTemplateUpdateManyWithoutUserNestedInput + ownedProjects?: Prisma.ProjectUpdateManyWithoutOwnerNestedInput + projectMembers?: Prisma.ProjectMemberUpdateManyWithoutUserNestedInput + createdTasks?: Prisma.TaskUpdateManyWithoutCreatorNestedInput + taskMembers?: Prisma.TaskMemberUpdateManyWithoutUserNestedInput +} + +export type UserUncheckedUpdateWithoutAssignedTasksInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habits?: Prisma.HabitUncheckedUpdateManyWithoutUserNestedInput + checkIns?: Prisma.CheckInUncheckedUpdateManyWithoutUserNestedInput + checkLists?: Prisma.CheckListUncheckedUpdateManyWithoutUserNestedInput + templates?: Prisma.CheckListTemplateUncheckedUpdateManyWithoutUserNestedInput + ownedProjects?: Prisma.ProjectUncheckedUpdateManyWithoutOwnerNestedInput + projectMembers?: Prisma.ProjectMemberUncheckedUpdateManyWithoutUserNestedInput + createdTasks?: Prisma.TaskUncheckedUpdateManyWithoutCreatorNestedInput + taskMembers?: Prisma.TaskMemberUncheckedUpdateManyWithoutUserNestedInput +} + +export type UserUpsertWithoutCreatedTasksInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.UserWhereInput +} + +export type UserUpdateToOneWithWhereWithoutCreatedTasksInput = { + where?: Prisma.UserWhereInput + data: Prisma.XOR +} + +export type UserUpdateWithoutCreatedTasksInput = { + name?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habits?: Prisma.HabitUpdateManyWithoutUserNestedInput + checkIns?: Prisma.CheckInUpdateManyWithoutUserNestedInput + checkLists?: Prisma.CheckListUpdateManyWithoutUserNestedInput + templates?: Prisma.CheckListTemplateUpdateManyWithoutUserNestedInput + ownedProjects?: Prisma.ProjectUpdateManyWithoutOwnerNestedInput + projectMembers?: Prisma.ProjectMemberUpdateManyWithoutUserNestedInput + assignedTasks?: Prisma.TaskUpdateManyWithoutAssigneeNestedInput + taskMembers?: Prisma.TaskMemberUpdateManyWithoutUserNestedInput +} + +export type UserUncheckedUpdateWithoutCreatedTasksInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habits?: Prisma.HabitUncheckedUpdateManyWithoutUserNestedInput + checkIns?: Prisma.CheckInUncheckedUpdateManyWithoutUserNestedInput + checkLists?: Prisma.CheckListUncheckedUpdateManyWithoutUserNestedInput + templates?: Prisma.CheckListTemplateUncheckedUpdateManyWithoutUserNestedInput + ownedProjects?: Prisma.ProjectUncheckedUpdateManyWithoutOwnerNestedInput + projectMembers?: Prisma.ProjectMemberUncheckedUpdateManyWithoutUserNestedInput + assignedTasks?: Prisma.TaskUncheckedUpdateManyWithoutAssigneeNestedInput + taskMembers?: Prisma.TaskMemberUncheckedUpdateManyWithoutUserNestedInput +} + +export type UserCreateWithoutTaskMembersInput = { + name: string + email: string + password: string + createdAt?: Date | string + updatedAt?: Date | string + habits?: Prisma.HabitCreateNestedManyWithoutUserInput + checkIns?: Prisma.CheckInCreateNestedManyWithoutUserInput + checkLists?: Prisma.CheckListCreateNestedManyWithoutUserInput + templates?: Prisma.CheckListTemplateCreateNestedManyWithoutUserInput + ownedProjects?: Prisma.ProjectCreateNestedManyWithoutOwnerInput + projectMembers?: Prisma.ProjectMemberCreateNestedManyWithoutUserInput + assignedTasks?: Prisma.TaskCreateNestedManyWithoutAssigneeInput + createdTasks?: Prisma.TaskCreateNestedManyWithoutCreatorInput +} + +export type UserUncheckedCreateWithoutTaskMembersInput = { + id?: number + name: string + email: string + password: string + createdAt?: Date | string + updatedAt?: Date | string + habits?: Prisma.HabitUncheckedCreateNestedManyWithoutUserInput + checkIns?: Prisma.CheckInUncheckedCreateNestedManyWithoutUserInput + checkLists?: Prisma.CheckListUncheckedCreateNestedManyWithoutUserInput + templates?: Prisma.CheckListTemplateUncheckedCreateNestedManyWithoutUserInput + ownedProjects?: Prisma.ProjectUncheckedCreateNestedManyWithoutOwnerInput + projectMembers?: Prisma.ProjectMemberUncheckedCreateNestedManyWithoutUserInput + assignedTasks?: Prisma.TaskUncheckedCreateNestedManyWithoutAssigneeInput + createdTasks?: Prisma.TaskUncheckedCreateNestedManyWithoutCreatorInput +} + +export type UserCreateOrConnectWithoutTaskMembersInput = { + where: Prisma.UserWhereUniqueInput + create: Prisma.XOR +} + +export type UserUpsertWithoutTaskMembersInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.UserWhereInput +} + +export type UserUpdateToOneWithWhereWithoutTaskMembersInput = { + where?: Prisma.UserWhereInput + data: Prisma.XOR +} + +export type UserUpdateWithoutTaskMembersInput = { + name?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habits?: Prisma.HabitUpdateManyWithoutUserNestedInput + checkIns?: Prisma.CheckInUpdateManyWithoutUserNestedInput + checkLists?: Prisma.CheckListUpdateManyWithoutUserNestedInput + templates?: Prisma.CheckListTemplateUpdateManyWithoutUserNestedInput + ownedProjects?: Prisma.ProjectUpdateManyWithoutOwnerNestedInput + projectMembers?: Prisma.ProjectMemberUpdateManyWithoutUserNestedInput + assignedTasks?: Prisma.TaskUpdateManyWithoutAssigneeNestedInput + createdTasks?: Prisma.TaskUpdateManyWithoutCreatorNestedInput +} + +export type UserUncheckedUpdateWithoutTaskMembersInput = { + id?: Prisma.IntFieldUpdateOperationsInput | number + name?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + habits?: Prisma.HabitUncheckedUpdateManyWithoutUserNestedInput + checkIns?: Prisma.CheckInUncheckedUpdateManyWithoutUserNestedInput + checkLists?: Prisma.CheckListUncheckedUpdateManyWithoutUserNestedInput + templates?: Prisma.CheckListTemplateUncheckedUpdateManyWithoutUserNestedInput + ownedProjects?: Prisma.ProjectUncheckedUpdateManyWithoutOwnerNestedInput + projectMembers?: Prisma.ProjectMemberUncheckedUpdateManyWithoutUserNestedInput + assignedTasks?: Prisma.TaskUncheckedUpdateManyWithoutAssigneeNestedInput + createdTasks?: Prisma.TaskUncheckedUpdateManyWithoutCreatorNestedInput +} + + +/** + * Count Type UserCountOutputType + */ + +export type UserCountOutputType = { + habits: number + checkIns: number + checkLists: number + templates: number + ownedProjects: number + projectMembers: number + assignedTasks: number + createdTasks: number + taskMembers: number +} + +export type UserCountOutputTypeSelect = { + habits?: boolean | UserCountOutputTypeCountHabitsArgs + checkIns?: boolean | UserCountOutputTypeCountCheckInsArgs + checkLists?: boolean | UserCountOutputTypeCountCheckListsArgs + templates?: boolean | UserCountOutputTypeCountTemplatesArgs + ownedProjects?: boolean | UserCountOutputTypeCountOwnedProjectsArgs + projectMembers?: boolean | UserCountOutputTypeCountProjectMembersArgs + assignedTasks?: boolean | UserCountOutputTypeCountAssignedTasksArgs + createdTasks?: boolean | UserCountOutputTypeCountCreatedTasksArgs + taskMembers?: boolean | UserCountOutputTypeCountTaskMembersArgs +} + +/** + * UserCountOutputType without action + */ +export type UserCountOutputTypeDefaultArgs = { + /** + * Select specific fields to fetch from the UserCountOutputType + */ + select?: Prisma.UserCountOutputTypeSelect | null +} + +/** + * UserCountOutputType without action + */ +export type UserCountOutputTypeCountHabitsArgs = { + where?: Prisma.HabitWhereInput +} + +/** + * UserCountOutputType without action + */ +export type UserCountOutputTypeCountCheckInsArgs = { + where?: Prisma.CheckInWhereInput +} + +/** + * UserCountOutputType without action + */ +export type UserCountOutputTypeCountCheckListsArgs = { + where?: Prisma.CheckListWhereInput +} + +/** + * UserCountOutputType without action + */ +export type UserCountOutputTypeCountTemplatesArgs = { + where?: Prisma.CheckListTemplateWhereInput +} + +/** + * UserCountOutputType without action + */ +export type UserCountOutputTypeCountOwnedProjectsArgs = { + where?: Prisma.ProjectWhereInput +} + +/** + * UserCountOutputType without action + */ +export type UserCountOutputTypeCountProjectMembersArgs = { + where?: Prisma.ProjectMemberWhereInput +} + +/** + * UserCountOutputType without action + */ +export type UserCountOutputTypeCountAssignedTasksArgs = { + where?: Prisma.TaskWhereInput +} + +/** + * UserCountOutputType without action + */ +export type UserCountOutputTypeCountCreatedTasksArgs = { + where?: Prisma.TaskWhereInput +} + +/** + * UserCountOutputType without action + */ +export type UserCountOutputTypeCountTaskMembersArgs = { + where?: Prisma.TaskMemberWhereInput +} export type UserSelect = runtime.Types.Extensions.GetSelect<{ @@ -400,9 +1435,35 @@ export type UserSelect + checkIns?: boolean | Prisma.User$checkInsArgs + checkLists?: boolean | Prisma.User$checkListsArgs + templates?: boolean | Prisma.User$templatesArgs + ownedProjects?: boolean | Prisma.User$ownedProjectsArgs + projectMembers?: boolean | Prisma.User$projectMembersArgs + assignedTasks?: boolean | Prisma.User$assignedTasksArgs + createdTasks?: boolean | Prisma.User$createdTasksArgs + taskMembers?: boolean | Prisma.User$taskMembersArgs + _count?: boolean | Prisma.UserCountOutputTypeDefaultArgs }, ExtArgs["result"]["user"]> +export type UserSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + name?: boolean + email?: boolean + password?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["user"]> +export type UserSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + name?: boolean + email?: boolean + password?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["user"]> export type UserSelectScalar = { id?: boolean @@ -414,10 +1475,34 @@ export type UserSelectScalar = { } export type UserOmit = runtime.Types.Extensions.GetOmit<"id" | "name" | "email" | "password" | "createdAt" | "updatedAt", ExtArgs["result"]["user"]> +export type UserInclude = { + habits?: boolean | Prisma.User$habitsArgs + checkIns?: boolean | Prisma.User$checkInsArgs + checkLists?: boolean | Prisma.User$checkListsArgs + templates?: boolean | Prisma.User$templatesArgs + ownedProjects?: boolean | Prisma.User$ownedProjectsArgs + projectMembers?: boolean | Prisma.User$projectMembersArgs + assignedTasks?: boolean | Prisma.User$assignedTasksArgs + createdTasks?: boolean | Prisma.User$createdTasksArgs + taskMembers?: boolean | Prisma.User$taskMembersArgs + _count?: boolean | Prisma.UserCountOutputTypeDefaultArgs +} +export type UserIncludeCreateManyAndReturn = {} +export type UserIncludeUpdateManyAndReturn = {} export type $UserPayload = { name: "User" - objects: {} + objects: { + habits: Prisma.$HabitPayload[] + checkIns: Prisma.$CheckInPayload[] + checkLists: Prisma.$CheckListPayload[] + templates: Prisma.$CheckListTemplatePayload[] + ownedProjects: Prisma.$ProjectPayload[] + projectMembers: Prisma.$ProjectMemberPayload[] + assignedTasks: Prisma.$TaskPayload[] + createdTasks: Prisma.$TaskPayload[] + taskMembers: Prisma.$TaskMemberPayload[] + } scalars: runtime.Types.Extensions.GetPayloadResult<{ id: number name: string @@ -542,6 +1627,30 @@ export interface UserDelegate(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Create many Users and returns the data saved in the database. + * @param {UserCreateManyAndReturnArgs} args - Arguments to create many Users. + * @example + * // Create many Users + * const user = await prisma.user.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many Users and only return the `id` + * const userWithIdOnly = await prisma.user.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + /** * Delete a User. * @param {UserDeleteArgs} args - Arguments to delete one User. @@ -606,6 +1715,36 @@ export interface UserDelegate(args: Prisma.SelectSubset>): Prisma.PrismaPromise + /** + * Update zero or more Users and returns the data updated in the database. + * @param {UserUpdateManyAndReturnArgs} args - Arguments to update many Users. + * @example + * // Update many Users + * const user = await prisma.user.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more Users and only return the `id` + * const userWithIdOnly = await prisma.user.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + /** * Create or update one User. * @param {UserUpsertArgs} args - Arguments to update or create a User. @@ -765,6 +1904,15 @@ readonly fields: UserFieldRefs; */ export interface Prisma__UserClient extends Prisma.PrismaPromise { readonly [Symbol.toStringTag]: "PrismaPromise" + habits = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + checkIns = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + checkLists = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + templates = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + ownedProjects = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + projectMembers = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + assignedTasks = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + createdTasks = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + taskMembers = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> /** * Attaches callbacks for the resolution and/or rejection of the Promise. * @param onfulfilled The callback to execute when the Promise is resolved. @@ -816,6 +1964,10 @@ export type UserFindUniqueArgs | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.UserInclude | null /** * Filter, which User to fetch. */ @@ -834,6 +1986,10 @@ export type UserFindUniqueOrThrowArgs | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.UserInclude | null /** * Filter, which User to fetch. */ @@ -852,6 +2008,10 @@ export type UserFindFirstArgs | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.UserInclude | null /** * Filter, which User to fetch. */ @@ -900,6 +2060,10 @@ export type UserFindFirstOrThrowArgs | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.UserInclude | null /** * Filter, which User to fetch. */ @@ -948,6 +2112,10 @@ export type UserFindManyArgs | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.UserInclude | null /** * Filter, which Users to fetch. */ @@ -996,6 +2164,10 @@ export type UserCreateArgs | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.UserInclude | null /** * The data needed to create a User. */ @@ -1013,6 +2185,25 @@ export type UserCreateManyArgs = { + /** + * Select specific fields to fetch from the User + */ + select?: Prisma.UserSelectCreateManyAndReturn | null + /** + * Omit specific fields from the User + */ + omit?: Prisma.UserOmit | null + /** + * The data used to create many Users. + */ + data: Prisma.UserCreateManyInput | Prisma.UserCreateManyInput[] + skipDuplicates?: boolean +} + /** * User update */ @@ -1025,6 +2216,10 @@ export type UserUpdateArgs | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.UserInclude | null /** * The data needed to update a User. */ @@ -1053,6 +2248,32 @@ export type UserUpdateManyArgs = { + /** + * Select specific fields to fetch from the User + */ + select?: Prisma.UserSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the User + */ + omit?: Prisma.UserOmit | null + /** + * The data used to update Users. + */ + data: Prisma.XOR + /** + * Filter which Users to update + */ + where?: Prisma.UserWhereInput + /** + * Limit how many Users to update. + */ + limit?: number +} + /** * User upsert */ @@ -1065,6 +2286,10 @@ export type UserUpsertArgs | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.UserInclude | null /** * The filter to search for the User to update in case it exists. */ @@ -1091,6 +2316,10 @@ export type UserDeleteArgs | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.UserInclude | null /** * Filter which User to delete. */ @@ -1111,6 +2340,222 @@ export type UserDeleteManyArgs = { + /** + * Select specific fields to fetch from the Habit + */ + select?: Prisma.HabitSelect | null + /** + * Omit specific fields from the Habit + */ + omit?: Prisma.HabitOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.HabitInclude | null + where?: Prisma.HabitWhereInput + orderBy?: Prisma.HabitOrderByWithRelationInput | Prisma.HabitOrderByWithRelationInput[] + cursor?: Prisma.HabitWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.HabitScalarFieldEnum | Prisma.HabitScalarFieldEnum[] +} + +/** + * User.checkIns + */ +export type User$checkInsArgs = { + /** + * Select specific fields to fetch from the CheckIn + */ + select?: Prisma.CheckInSelect | null + /** + * Omit specific fields from the CheckIn + */ + omit?: Prisma.CheckInOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckInInclude | null + where?: Prisma.CheckInWhereInput + orderBy?: Prisma.CheckInOrderByWithRelationInput | Prisma.CheckInOrderByWithRelationInput[] + cursor?: Prisma.CheckInWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.CheckInScalarFieldEnum | Prisma.CheckInScalarFieldEnum[] +} + +/** + * User.checkLists + */ +export type User$checkListsArgs = { + /** + * Select specific fields to fetch from the CheckList + */ + select?: Prisma.CheckListSelect | null + /** + * Omit specific fields from the CheckList + */ + omit?: Prisma.CheckListOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListInclude | null + where?: Prisma.CheckListWhereInput + orderBy?: Prisma.CheckListOrderByWithRelationInput | Prisma.CheckListOrderByWithRelationInput[] + cursor?: Prisma.CheckListWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.CheckListScalarFieldEnum | Prisma.CheckListScalarFieldEnum[] +} + +/** + * User.templates + */ +export type User$templatesArgs = { + /** + * Select specific fields to fetch from the CheckListTemplate + */ + select?: Prisma.CheckListTemplateSelect | null + /** + * Omit specific fields from the CheckListTemplate + */ + omit?: Prisma.CheckListTemplateOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CheckListTemplateInclude | null + where?: Prisma.CheckListTemplateWhereInput + orderBy?: Prisma.CheckListTemplateOrderByWithRelationInput | Prisma.CheckListTemplateOrderByWithRelationInput[] + cursor?: Prisma.CheckListTemplateWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.CheckListTemplateScalarFieldEnum | Prisma.CheckListTemplateScalarFieldEnum[] +} + +/** + * User.ownedProjects + */ +export type User$ownedProjectsArgs = { + /** + * Select specific fields to fetch from the Project + */ + select?: Prisma.ProjectSelect | null + /** + * Omit specific fields from the Project + */ + omit?: Prisma.ProjectOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectInclude | null + where?: Prisma.ProjectWhereInput + orderBy?: Prisma.ProjectOrderByWithRelationInput | Prisma.ProjectOrderByWithRelationInput[] + cursor?: Prisma.ProjectWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.ProjectScalarFieldEnum | Prisma.ProjectScalarFieldEnum[] +} + +/** + * User.projectMembers + */ +export type User$projectMembersArgs = { + /** + * Select specific fields to fetch from the ProjectMember + */ + select?: Prisma.ProjectMemberSelect | null + /** + * Omit specific fields from the ProjectMember + */ + omit?: Prisma.ProjectMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.ProjectMemberInclude | null + where?: Prisma.ProjectMemberWhereInput + orderBy?: Prisma.ProjectMemberOrderByWithRelationInput | Prisma.ProjectMemberOrderByWithRelationInput[] + cursor?: Prisma.ProjectMemberWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.ProjectMemberScalarFieldEnum | Prisma.ProjectMemberScalarFieldEnum[] +} + +/** + * User.assignedTasks + */ +export type User$assignedTasksArgs = { + /** + * Select specific fields to fetch from the Task + */ + select?: Prisma.TaskSelect | null + /** + * Omit specific fields from the Task + */ + omit?: Prisma.TaskOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskInclude | null + where?: Prisma.TaskWhereInput + orderBy?: Prisma.TaskOrderByWithRelationInput | Prisma.TaskOrderByWithRelationInput[] + cursor?: Prisma.TaskWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.TaskScalarFieldEnum | Prisma.TaskScalarFieldEnum[] +} + +/** + * User.createdTasks + */ +export type User$createdTasksArgs = { + /** + * Select specific fields to fetch from the Task + */ + select?: Prisma.TaskSelect | null + /** + * Omit specific fields from the Task + */ + omit?: Prisma.TaskOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskInclude | null + where?: Prisma.TaskWhereInput + orderBy?: Prisma.TaskOrderByWithRelationInput | Prisma.TaskOrderByWithRelationInput[] + cursor?: Prisma.TaskWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.TaskScalarFieldEnum | Prisma.TaskScalarFieldEnum[] +} + +/** + * User.taskMembers + */ +export type User$taskMembersArgs = { + /** + * Select specific fields to fetch from the TaskMember + */ + select?: Prisma.TaskMemberSelect | null + /** + * Omit specific fields from the TaskMember + */ + omit?: Prisma.TaskMemberOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.TaskMemberInclude | null + where?: Prisma.TaskMemberWhereInput + orderBy?: Prisma.TaskMemberOrderByWithRelationInput | Prisma.TaskMemberOrderByWithRelationInput[] + cursor?: Prisma.TaskMemberWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.TaskMemberScalarFieldEnum | Prisma.TaskMemberScalarFieldEnum[] +} + /** * User without action */ @@ -1123,4 +2568,8 @@ export type UserDefaultArgs | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.UserInclude | null } diff --git a/index.html b/index.html index 8faf2fb..9d8210e 100644 --- a/index.html +++ b/index.html @@ -1,14 +1,14 @@ - - - - - - - - iBoard - 经济数据与策略 - - -
- - + + + + + + + + iBoard - 经济数据与策略 + + +
+ + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1531417..b040951 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,17 @@ { "name": "iboard", - "version": "1.0.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "iboard", - "version": "1.0.0", + "version": "0.2.0", "dependencies": { - "@prisma/adapter-mariadb": "^7.9.1", + "@journeyapps/wa-sqlite": "^2.0.7", + "@powersync/react": "^2.1.0", + "@powersync/web": "^2.5.0", + "@prisma/adapter-pg": "^7.9.1", "@prisma/client": "^7.7.0", "@zythum02/mijia-api": "^0.1.4", "antd-mobile": "^5.42.3", @@ -1187,6 +1190,11 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@journeyapps/wa-sqlite": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@journeyapps/wa-sqlite/-/wa-sqlite-2.0.8.tgz", + "integrity": "sha512-WRXEms2515S/0DFSWzmlggHu9hSJomhugDUO17rWkL6VpokV/hfjIDLs954Egx/eqrk3WADmiOMzOc7MuJbznQ==" + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1615,64 +1623,91 @@ "node": "^22.20 || ^24.12 || >=25" } }, - "node_modules/@prisma/adapter-mariadb": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@prisma/adapter-mariadb/-/adapter-mariadb-7.9.1.tgz", - "integrity": "sha512-4aZUbIg2av7XFSQyM9cQeAp9YP9HiPr8H7ehe2rDo375IZXILEzQrNXZ/zLetgRQ2ojlaUl/brArFdOVS3n1xA==", + "node_modules/@powersync/common": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@powersync/common/-/common-2.4.0.tgz", + "integrity": "sha512-ngVogy+B8NQeI5l+0Es8qMxnGozUie8Q6R4+0/AHA3oBAhDG5UmeYw/LquTzZJSOPnCmvGgFSqovblV+JKzxKQ==", + "license": "Apache-2.0" + }, + "node_modules/@powersync/react": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@powersync/react/-/react-2.1.0.tgz", + "integrity": "sha512-ADIpakSLhstv0F9xrUIF+oCArFnAQPrcB9mNql6xH0Nup9EgBug38Ks+mv3yqbc33z60pBm9tuHs8uSIbkhuqg==", + "license": "Apache-2.0", + "peerDependencies": { + "@powersync/common": "^2.4.0", + "react": "*" + } + }, + "node_modules/@powersync/shared-internals": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@powersync/shared-internals/-/shared-internals-1.4.0.tgz", + "integrity": "sha512-OiqqDuW6tePW4Ed7C5IWutBKrtcqKuTMlVMQNwUZtKtqr7ZrqKlb+YP5l4niuz8mpqDYo3/HNU0dpc+3mneSBQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@powersync/common": "^2.4.0" + } + }, + "node_modules/@powersync/web": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@powersync/web/-/web-2.5.0.tgz", + "integrity": "sha512-/K2ZUjIdqvtFeoLfPetPcE+uioShBWzrTrOa8hpoZ7i62S8wgj47i+VPnKTXI7WQmXuxTGGZFUFnahinf6wjPg==", "license": "Apache-2.0", "dependencies": { - "@prisma/driver-adapter-utils": "7.9.1", - "mariadb": "3.4.5" - } - }, - "node_modules/@prisma/adapter-mariadb/node_modules/@types/node": { - "version": "24.13.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", - "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.18.0" - } - }, - "node_modules/@prisma/adapter-mariadb/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "@journeyapps/wa-sqlite": "2.0.7", + "@powersync/common": "2.4.0", + "@powersync/shared-internals": "1.4.0", + "comlink": "^4.4.2", + "commander": "^12.1.0" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@prisma/adapter-mariadb/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/@prisma/adapter-mariadb/node_modules/mariadb": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/mariadb/-/mariadb-3.4.5.tgz", - "integrity": "sha512-gThTYkhIS5rRqkVr+Y0cIdzr+GRqJ9sA2Q34e0yzmyhMCwyApf3OKAC1jnF23aSlIOqJuyaUFUcj7O1qZslmmQ==", - "license": "LGPL-2.1-or-later", - "dependencies": { - "@types/geojson": "^7946.0.16", - "@types/node": "^24.0.13", - "denque": "^2.1.0", - "iconv-lite": "^0.6.3", - "lru-cache": "^10.4.3" + "bin": { + "powersync-web": "bin/powersync.cjs" }, - "engines": { - "node": ">= 14" + "peerDependencies": { + "@powersync/common": "^2.4.0", + "@powersync/shared-internals": "^1.4.0" } }, - "node_modules/@prisma/adapter-mariadb/node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "license": "MIT" + "node_modules/@powersync/web/node_modules/@journeyapps/wa-sqlite": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@journeyapps/wa-sqlite/-/wa-sqlite-2.0.7.tgz", + "integrity": "sha512-mc6xUricl4QB2s8QTRGS+FofSr1cjwDtf6dPXAdMBuGWodjfEgkSJGDPWQ7zVqgc7hMfXYLT7WIfEBAxara8YA==" + }, + "node_modules/@powersync/web/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@prisma/adapter-pg": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.10.1.tgz", + "integrity": "sha512-EP9ruPYHjKP80WZlb5qVaEXKgZjJieBFjAxcBtQnDAHD6+16UN4keVmBh6L/RUpuDQT2FQRbvKNgq5d4Ix+IkQ==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/driver-adapter-utils": "7.10.1", + "@types/pg": "^8.16.0", + "pg": "^8.16.3", + "postgres-array": "3.0.4" + } + }, + "node_modules/@prisma/adapter-pg/node_modules/@prisma/debug": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.10.1.tgz", + "integrity": "sha512-eFCAkGZ13D/qPg48JBlG5IWi9cSBBOC7oG3abDPcAkYioDWlIjh0YJJLK2J7qo5S+xWWD8I6egAN/8KbCaiMCA==", + "license": "Apache-2.0" + }, + "node_modules/@prisma/adapter-pg/node_modules/@prisma/driver-adapter-utils": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.10.1.tgz", + "integrity": "sha512-DJ4c6xvVnm3s9ZUyql9nCUicADSqyvH+CjXRCM9L6m5zJxCNmebCElG6X4irICq3rosBwlX+S19Dxq/4IrZk3A==", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.10.1" + } }, "node_modules/@prisma/client": { "version": "7.7.0", @@ -1747,21 +1782,6 @@ "zeptomatch": "2.1.0" } }, - "node_modules/@prisma/driver-adapter-utils": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.9.1.tgz", - "integrity": "sha512-vmHehG7nn/heW32DXXpp13DxxAxVVe6n250oEt3dOL2E/4bt3olktKZN0mzSuxMMronyMSkbeW2uCOn3F4g8RQ==", - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.9.1" - } - }, - "node_modules/@prisma/driver-adapter-utils/node_modules/@prisma/debug": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.9.1.tgz", - "integrity": "sha512-/cpVZ4itxtcgB8GHBvZtcmuEjq+lWsLrRJxFMbwZrT1RIdtuKmUm7PPGo/wzfbYpBrk+9WmmBE8CHJw2rybKDQ==", - "license": "Apache-2.0" - }, "node_modules/@prisma/engines": { "version": "7.7.0", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.7.0.tgz", @@ -2867,6 +2887,17 @@ "undici-types": "~8.3.0" } }, + "node_modules/@types/pg": { + "version": "8.23.1", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.23.1.tgz", + "integrity": "sha512-fKVHpikPdg4GKks3JuLEhvwSyvwzF23hnabPy6DD8ljVbC7+6J5dQzdv4arV6jqq57djnMgs1HKBxX4P8aBI3A==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/react": { "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", @@ -3193,6 +3224,21 @@ "integrity": "sha512-YOf0VSj5nUPI27doTtXF+BBnsiRq3qY7avHqfIWnppxTLGyvkLq1QV2RTxkwoZwJ60ywLfZ0raFF4J/G886i7A==", "license": "MIT" }, + "node_modules/better-sqlite3": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-13.0.3.tgz", + "integrity": "sha512-RbOBxmLBG8uvFUc15X9+9SFemKcQ0WBuISBVkpuiaUB2qblC8UWlHEjdWVoZ8AdhSwmoEgsiXKfopX0CQxaACQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "node-addon-api": "^8.0.0" + }, + "engines": { + "node": ">=22" + } + }, "node_modules/body-parser": { "version": "1.20.6", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.6.tgz", @@ -3575,6 +3621,12 @@ "node": ">= 0.8" } }, + "node_modules/comlink": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/comlink/-/comlink-4.4.2.tgz", + "integrity": "sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g==", + "license": "Apache-2.0" + }, "node_modules/commander": { "version": "13.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", @@ -5362,6 +5414,17 @@ "node": ">= 0.6" } }, + "node_modules/node-addon-api": { + "version": "8.9.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.2.tgz", + "integrity": "sha512-VijLXbi3UACN69I0JVXJsX4tjACjNoQDgv2gTF6sx2wWEi8tkSg2eX8p5gSIFi8z2+DL3oHmY6OyKce38SDolg==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, "node_modules/node-cron": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", @@ -5579,6 +5642,104 @@ "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", "license": "MIT" }, + "node_modules/pg": { + "version": "8.23.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.23.1.tgz", + "integrity": "sha512-aL96AHANtWjPLDOLqnhx+ngp9+UK7ETEU8VJrDCGvsSSi/mGLcWYsS6Herg7lmaBJe4uwrfqsa7gTEFaSizDoQ==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.14.1", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.16.1", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.4.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.1.tgz", + "integrity": "sha512-6PQbsFWZcp9EmJEwy5cGQ2La+AMWpP46lgbb8X+U/XsHIUweYDNCpeuKck5RxL2MdVFi7krbbEi5nX4Zh7JhrQ==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.1.tgz", + "integrity": "sha512-qR3kGNPBLpCNtz0evbKA0Y/MRFXwSSdT+pTJvYp/bXTcReZbvX1kzF0IyTc1QnxqF7AZbOeBhNL8R5mYQZV/MA==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.16.1.tgz", + "integrity": "sha512-p9VOFMiHB/ZbJATetbg+99PxssTVSQRnyuPSQ67mN1+1KBOjZaZ83ZQzltnxPhJwSsC3nwVjJ10DVJlerbFzLg==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-types/node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5619,6 +5780,45 @@ "url": "https://github.com/sponsors/porsager" } }, + "node_modules/postgres-array": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz", + "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prisma": { "version": "7.7.0", "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.7.0.tgz", @@ -6470,6 +6670,15 @@ "node": ">=0.10.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -6938,6 +7147,15 @@ } } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 6f6c6bd..924b717 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iboard", - "version": "1.0.0", + "version": "0.2.1", "type": "module", "scripts": { "tauri": "tauri", @@ -8,6 +8,9 @@ "dev:server": "node --watch --experimental-strip-types --no-warnings server/index.js", "dev": "concurrently -n web,api -c blue,green \"npm run dev:web\" \"npm run dev:server\"", "build": "vite build", + "build:shell": "vite build --mode shell", + "build:win": "node scripts/build-windows.mjs", + "build:win:debug": "node scripts/build-windows.mjs --debug", "build:ota": "node scripts/build-ota.mjs", "preview": "vite preview", "start": "node --experimental-strip-types --no-warnings server/index.js", @@ -30,7 +33,10 @@ "publish": "bash scripts/wsl/build-apk.sh release" }, "dependencies": { - "@prisma/adapter-mariadb": "^7.9.1", + "@journeyapps/wa-sqlite": "^2.0.7", + "@powersync/react": "^2.1.0", + "@powersync/web": "^2.5.0", + "@prisma/adapter-pg": "^7.9.1", "@prisma/client": "^7.7.0", "@zythum02/mijia-api": "^0.1.4", "antd-mobile": "^5.42.3", @@ -52,8 +58,8 @@ "ws": "^8.21.2" }, "devDependencies": { - "@tauri-apps/api": "^2.0.0", - "@tauri-apps/cli": "^2.0.0", + "@tauri-apps/api": "^2.12.4", + "@tauri-apps/cli": "^2.12.4", "@vitejs/plugin-react": "^4.3.1", "cheerio": "^1.2.0", "concurrently": "^9.0.0", diff --git a/prisma/migrations/20260409135419_init/migration.sql b/prisma/migrations/20260409135419_init/migration.sql deleted file mode 100644 index c463b12..0000000 --- a/prisma/migrations/20260409135419_init/migration.sql +++ /dev/null @@ -1,62 +0,0 @@ --- CreateTable -CREATE TABLE `User` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `name` VARCHAR(191) NOT NULL, - `email` VARCHAR(191) NOT NULL, - `password` VARCHAR(191) NOT NULL, - `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - `updatedAt` DATETIME(3) NOT NULL, - - UNIQUE INDEX `User_email_key`(`email`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- CreateTable -CREATE TABLE `Article` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `title` VARCHAR(191) NOT NULL, - `slug` VARCHAR(191) NOT NULL, - `content` VARCHAR(191) NOT NULL, - `date` VARCHAR(191) NOT NULL, - `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - `updatedAt` DATETIME(3) NOT NULL, - - UNIQUE INDEX `Article_slug_key`(`slug`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- CreateTable -CREATE TABLE `Strategy` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `name` VARCHAR(191) NOT NULL, - `description` VARCHAR(191) NOT NULL, - `data` JSON NOT NULL, - `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - `updatedAt` DATETIME(3) NOT NULL, - - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- CreateTable -CREATE TABLE `Indicator` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `name` VARCHAR(191) NOT NULL, - `groupName` VARCHAR(191) NOT NULL, - `data` JSON NOT NULL, - `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - `updatedAt` DATETIME(3) NOT NULL, - - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- CreateTable -CREATE TABLE `SowInventory` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `month` VARCHAR(191) NOT NULL, - `inventory` INTEGER NOT NULL, - `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - `updatedAt` DATETIME(3) NOT NULL, - - UNIQUE INDEX `SowInventory_month_key`(`month`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; diff --git a/prisma/migrations/20260409135954_rename_sow_inventory/migration.sql b/prisma/migrations/20260409135954_rename_sow_inventory/migration.sql deleted file mode 100644 index 76bead8..0000000 --- a/prisma/migrations/20260409135954_rename_sow_inventory/migration.sql +++ /dev/null @@ -1,20 +0,0 @@ -/* - Warnings: - - - You are about to drop the `SowInventory` table. If the table is not empty, all the data it contains will be lost. - -*/ --- DropTable -DROP TABLE `SowInventory`; - --- CreateTable -CREATE TABLE `Econ_SowInventory` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `month` VARCHAR(191) NOT NULL, - `inventory` INTEGER NOT NULL, - `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - `updatedAt` DATETIME(3) NOT NULL, - - UNIQUE INDEX `Econ_SowInventory_month_key`(`month`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; diff --git a/prisma/migrations/20260417122901_add_scraper_cache/migration.sql b/prisma/migrations/20260417122901_add_scraper_cache/migration.sql deleted file mode 100644 index d7210d8..0000000 --- a/prisma/migrations/20260417122901_add_scraper_cache/migration.sql +++ /dev/null @@ -1,13 +0,0 @@ --- CreateTable -CREATE TABLE `ScraperCache` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `scraperName` VARCHAR(191) NOT NULL, - `lastDataHash` VARCHAR(191) NULL, - `lastUpdateAt` DATETIME(3) NOT NULL, - `metadata` JSON NULL, - `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - `updatedAt` DATETIME(3) NOT NULL, - - UNIQUE INDEX `ScraperCache_scraperName_key`(`scraperName`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; diff --git a/prisma/migrations/20260803144851_add_unique_name/migration.sql b/prisma/migrations/20260803144851_add_unique_name/migration.sql deleted file mode 100644 index 0ef93bc..0000000 --- a/prisma/migrations/20260803144851_add_unique_name/migration.sql +++ /dev/null @@ -1,5 +0,0 @@ --- Add unique constraint on Strategy.name to enable idempotent upsert in seed script -ALTER TABLE `Strategy` ADD UNIQUE INDEX `Strategy_name_key`(`name`); - --- Add unique constraint on Indicator.name to enable idempotent upsert in seed script -ALTER TABLE `Indicator` ADD UNIQUE INDEX `Indicator_name_key`(`name`); \ No newline at end of file diff --git a/prisma/migrations/20260806120000_indicator_points/migration.sql b/prisma/migrations/20260806120000_indicator_points/migration.sql deleted file mode 100644 index b0f587f..0000000 --- a/prisma/migrations/20260806120000_indicator_points/migration.sql +++ /dev/null @@ -1,24 +0,0 @@ --- 经济指标: 明细数据拆到独立表, 兼容年/季/月/日频率 --- 第一步: 建 IndicatorPoint 表 + Indicator 加元数据列 (data 列暂保留, 由迁移脚本搬运后删除) - --- CreateTable -CREATE TABLE `IndicatorPoint` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `indicatorId` INTEGER NOT NULL, - `period` VARCHAR(191) NOT NULL, - `value` DOUBLE NOT NULL, - `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - `updatedAt` DATETIME(3) NOT NULL, - - UNIQUE INDEX `IndicatorPoint_indicatorId_period_key`(`indicatorId`, `period`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- AlterTable: 元数据列 (data JSON 拆出) -ALTER TABLE `Indicator` ADD COLUMN `title` VARCHAR(191) NOT NULL DEFAULT '', -ADD COLUMN `unit` VARCHAR(191) NOT NULL DEFAULT '', -ADD COLUMN `source` VARCHAR(191) NOT NULL DEFAULT '', -ADD COLUMN `frequency` VARCHAR(191) NOT NULL DEFAULT 'month'; - --- AddForeignKey -ALTER TABLE `IndicatorPoint` ADD CONSTRAINT `IndicatorPoint_indicatorId_fkey` FOREIGN KEY (`indicatorId`) REFERENCES `Indicator`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20260806121000_drop_indicator_data_json/migration.sql b/prisma/migrations/20260806121000_drop_indicator_data_json/migration.sql deleted file mode 100644 index 07c9664..0000000 --- a/prisma/migrations/20260806121000_drop_indicator_data_json/migration.sql +++ /dev/null @@ -1,3 +0,0 @@ --- 第二步: 明细数据已迁移到 IndicatorPoint, 删除 Indicator.data JSON 列 --- AlterTable -ALTER TABLE `Indicator` DROP COLUMN `data`; diff --git a/prisma/migrations/20260806123000_add_indicator_hidden/migration.sql b/prisma/migrations/20260806123000_add_indicator_hidden/migration.sql deleted file mode 100644 index f54a938..0000000 --- a/prisma/migrations/20260806123000_add_indicator_hidden/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- 指标隐藏标记: 数据与获取脚本保留, 前端展示过滤 hidden=true -ALTER TABLE `Indicator` ADD COLUMN `hidden` BOOLEAN NOT NULL DEFAULT false; diff --git a/prisma/migrations/20260806130000_news_and_alerts/migration.sql b/prisma/migrations/20260806130000_news_and_alerts/migration.sql deleted file mode 100644 index 6c9aeaa..0000000 --- a/prisma/migrations/20260806130000_news_and_alerts/migration.sql +++ /dev/null @@ -1,32 +0,0 @@ --- CreateTable -CREATE TABLE `News` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `title` VARCHAR(191) NOT NULL, - `summary` VARCHAR(191) NOT NULL DEFAULT '', - `source` VARCHAR(191) NOT NULL DEFAULT '', - `url` VARCHAR(191) NOT NULL DEFAULT '', - `publishedAt` DATETIME(3) NOT NULL, - `impactRank` INTEGER NOT NULL, - `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - - UNIQUE INDEX `News_title_publishedAt_key`(`title`, `publishedAt`), - INDEX `News_publishedAt_idx`(`publishedAt`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- CreateTable -CREATE TABLE `Alert` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `indicatorName` VARCHAR(191) NOT NULL, - `period` VARCHAR(191) NOT NULL, - `valueA` DOUBLE NOT NULL, - `valueB` DOUBLE NOT NULL, - `sourceA` VARCHAR(191) NOT NULL, - `sourceB` VARCHAR(191) NOT NULL, - `diff` DOUBLE NOT NULL, - `diffRatio` DOUBLE NOT NULL, - `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - - INDEX `Alert_createdAt_idx`(`createdAt`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; diff --git a/prisma/migrations/20260806140000_monitors/migration.sql b/prisma/migrations/20260806140000_monitors/migration.sql deleted file mode 100644 index cc08bec..0000000 --- a/prisma/migrations/20260806140000_monitors/migration.sql +++ /dev/null @@ -1,68 +0,0 @@ --- 服务器监控 + 服务健康检查 --- CreateTable -CREATE TABLE `ServerNode` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `uuid` VARCHAR(191) NOT NULL, - `name` VARCHAR(191) NOT NULL, - `cpuName` VARCHAR(191) NOT NULL DEFAULT '', - `os` VARCHAR(191) NOT NULL DEFAULT '', - `region` VARCHAR(191) NOT NULL DEFAULT '', - `memTotal` BIGINT NOT NULL DEFAULT 0, - `diskTotal` BIGINT NOT NULL DEFAULT 0, - `cpuCores` INTEGER NOT NULL DEFAULT 0, - `cpuUsage` DOUBLE NOT NULL DEFAULT 0, - `ramUsed` BIGINT NOT NULL DEFAULT 0, - `swapUsed` BIGINT NOT NULL DEFAULT 0, - `load1` DOUBLE NOT NULL DEFAULT 0, - `load5` DOUBLE NOT NULL DEFAULT 0, - `load15` DOUBLE NOT NULL DEFAULT 0, - `diskUsed` BIGINT NOT NULL DEFAULT 0, - `netIn` BIGINT NOT NULL DEFAULT 0, - `netOut` BIGINT NOT NULL DEFAULT 0, - `uptime` BIGINT NOT NULL DEFAULT 0, - `process` INTEGER NOT NULL DEFAULT 0, - `connections` INTEGER NOT NULL DEFAULT 0, - `status` VARCHAR(191) NOT NULL DEFAULT 'unknown', - `recordedAt` DATETIME(3) NOT NULL, - `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - `updatedAt` DATETIME(3) NOT NULL, - - UNIQUE INDEX `ServerNode_uuid_key`(`uuid`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- CreateTable -CREATE TABLE `ServerNodeHistory` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `uuid` VARCHAR(191) NOT NULL, - `period` DATETIME(3) NOT NULL, - `cpu` DOUBLE NOT NULL DEFAULT 0, - `ram` BIGINT NOT NULL DEFAULT 0, - `load` DOUBLE NOT NULL DEFAULT 0, - `disk` BIGINT NOT NULL DEFAULT 0, - `netIn` BIGINT NOT NULL DEFAULT 0, - `netOut` BIGINT NOT NULL DEFAULT 0, - `process` INTEGER NOT NULL DEFAULT 0, - `connections` INTEGER NOT NULL DEFAULT 0, - `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - - UNIQUE INDEX `ServerNodeHistory_uuid_period_key`(`uuid`, `period`), - INDEX `ServerNodeHistory_uuid_period_idx`(`uuid`, `period`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- CreateTable -CREATE TABLE `ServiceCheck` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `name` VARCHAR(191) NOT NULL, - `url` VARCHAR(191) NOT NULL, - `status` VARCHAR(191) NOT NULL DEFAULT 'unknown', - `latencyMs` INTEGER NOT NULL DEFAULT 0, - `message` VARCHAR(191) NOT NULL DEFAULT '', - `lastCheckAt` DATETIME(3) NOT NULL, - `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - `updatedAt` DATETIME(3) NOT NULL, - - UNIQUE INDEX `ServiceCheck_name_key`(`name`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; diff --git a/prisma/migrations/20260806150000_add_news_content/migration.sql b/prisma/migrations/20260806150000_add_news_content/migration.sql deleted file mode 100644 index 143486d..0000000 --- a/prisma/migrations/20260806150000_add_news_content/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable (news.content 全文, 须在 news_and_alerts 建表之后执行) -ALTER TABLE `News` ADD COLUMN `content` TEXT NULL; diff --git a/prisma/migrations/20260807104432_add_oil_price_history/migration.sql b/prisma/migrations/20260807104432_add_oil_price_history/migration.sql deleted file mode 100644 index f2377cf..0000000 --- a/prisma/migrations/20260807104432_add_oil_price_history/migration.sql +++ /dev/null @@ -1,17 +0,0 @@ --- 浙江省油价历史表 --- - date: 调价日次日 (即数据实际抓取日, 便于按时间排序) --- - province + fuel: 浙江 / 92/95/0 --- - unique (date, province, fuel): 防 cron 重复入库 --- - DOUBLE 容纳 999.999 元/升 -CREATE TABLE OilPriceHistory ( - id INT NOT NULL AUTO_INCREMENT, - date DATETIME(3) NOT NULL, - province VARCHAR(191) NOT NULL, - fuel VARCHAR(8) NOT NULL, - price DOUBLE NOT NULL, - source VARCHAR(191) NOT NULL DEFAULT 'apizero', - createdAt DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - PRIMARY KEY (id), - UNIQUE INDEX OilPriceHistory_date_province_fuel_key(date, province, fuel), - INDEX OilPriceHistory_province_fuel_date_idx(province, fuel, date) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; diff --git a/prisma/migrations/20260920120000_add_mihome_env/migration.sql b/prisma/migrations/20260920120000_add_mihome_env/migration.sql deleted file mode 100644 index bb64515..0000000 --- a/prisma/migrations/20260920120000_add_mihome_env/migration.sql +++ /dev/null @@ -1,95 +0,0 @@ --- CreateTable -CREATE TABLE `MihomeAccount` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `key` VARCHAR(191) NOT NULL DEFAULT 'default', - `userId` VARCHAR(191) NOT NULL DEFAULT '', - `status` VARCHAR(191) NOT NULL DEFAULT 'unknown', - `lastError` VARCHAR(191) NOT NULL DEFAULT '', - `updatedAt` DATETIME(3) NOT NULL, - - UNIQUE INDEX `MihomeAccount_key_key`(`key`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- CreateTable -CREATE TABLE `MihomeHome` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `homeId` VARCHAR(191) NOT NULL, - `name` VARCHAR(191) NOT NULL, - `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - - UNIQUE INDEX `MihomeHome_homeId_key`(`homeId`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- CreateTable -CREATE TABLE `MihomeDevice` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `did` VARCHAR(191) NOT NULL, - `homeId` VARCHAR(191) NOT NULL, - `model` VARCHAR(191) NOT NULL, - `name` VARCHAR(191) NOT NULL, - `alias` VARCHAR(191) NULL, - `tempSiid` INTEGER NOT NULL, - `tempPiid` INTEGER NOT NULL, - `humSiid` INTEGER NOT NULL, - `humPiid` INTEGER NOT NULL, - `battSiid` INTEGER NOT NULL, - `battPiid` INTEGER NOT NULL, - `battery` INTEGER NULL, - `batteryAt` DATETIME(3) NULL, - `enabled` BOOLEAN NOT NULL DEFAULT true, - `lastSyncAt` DATETIME(3) NULL, - `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - - UNIQUE INDEX `MihomeDevice_did_key`(`did`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- CreateTable -CREATE TABLE `MihomeEnvSample` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `deviceId` INTEGER NOT NULL, - `date` DATETIME(3) NOT NULL, - `temperature` DOUBLE NULL, - `humidity` DOUBLE NULL, - `temperatureAvg` DOUBLE NULL, - `humidityAvg` DOUBLE NULL, - `temperatureMin` DOUBLE NULL, - `temperatureMax` DOUBLE NULL, - `humidityMin` DOUBLE NULL, - `humidityMax` DOUBLE NULL, - `origin` VARCHAR(191) NOT NULL DEFAULT 'live', - `sampledAt` DATETIME(3) NULL, - `raw` JSON NULL, - `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - - INDEX `MihomeEnvSample_deviceId_date_idx`(`deviceId`, `date`), - UNIQUE INDEX `MihomeEnvSample_deviceId_date_key`(`deviceId`, `date`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- CreateTable -CREATE TABLE `MihomeEnvHour` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `deviceId` INTEGER NOT NULL, - `hour` DATETIME(3) NOT NULL, - `temperatureAvg` DOUBLE NULL, - `humidityAvg` DOUBLE NULL, - `raw` JSON NULL, - `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - - INDEX `MihomeEnvHour_deviceId_hour_idx`(`deviceId`, `hour`), - UNIQUE INDEX `MihomeEnvHour_deviceId_hour_key`(`deviceId`, `hour`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- AddForeignKey -ALTER TABLE `MihomeDevice` ADD CONSTRAINT `MihomeDevice_homeId_fkey` FOREIGN KEY (`homeId`) REFERENCES `MihomeHome`(`homeId`) ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE `MihomeEnvSample` ADD CONSTRAINT `MihomeEnvSample_deviceId_fkey` FOREIGN KEY (`deviceId`) REFERENCES `MihomeDevice`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE `MihomeEnvHour` ADD CONSTRAINT `MihomeEnvHour_deviceId_fkey` FOREIGN KEY (`deviceId`) REFERENCES `MihomeDevice`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; - diff --git a/prisma/migrations/20261026000000_postgres_baseline/migration.sql b/prisma/migrations/20261026000000_postgres_baseline/migration.sql new file mode 100644 index 0000000..87c1af5 --- /dev/null +++ b/prisma/migrations/20261026000000_postgres_baseline/migration.sql @@ -0,0 +1,655 @@ +-- CreateSchema +CREATE SCHEMA IF NOT EXISTS "public"; + +-- CreateTable +CREATE TABLE "User" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "email" TEXT NOT NULL, + "password" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Article" ( + "id" SERIAL NOT NULL, + "title" TEXT NOT NULL, + "slug" TEXT NOT NULL, + "content" TEXT NOT NULL, + "date" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Article_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Strategy" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT NOT NULL, + "data" JSONB NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Strategy_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Indicator" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "groupName" TEXT NOT NULL, + "title" TEXT NOT NULL DEFAULT '', + "unit" TEXT NOT NULL DEFAULT '', + "source" TEXT NOT NULL DEFAULT '', + "frequency" TEXT NOT NULL DEFAULT 'month', + "hidden" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Indicator_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "IndicatorPoint" ( + "id" SERIAL NOT NULL, + "indicatorId" INTEGER NOT NULL, + "period" TEXT NOT NULL, + "value" DOUBLE PRECISION NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "IndicatorPoint_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "News" ( + "id" SERIAL NOT NULL, + "title" TEXT NOT NULL, + "summary" TEXT NOT NULL DEFAULT '', + "content" TEXT, + "source" TEXT NOT NULL DEFAULT '', + "url" TEXT NOT NULL DEFAULT '', + "publishedAt" TIMESTAMP(3) NOT NULL, + "impactRank" INTEGER NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "News_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Alert" ( + "id" SERIAL NOT NULL, + "indicatorName" TEXT NOT NULL, + "period" TEXT NOT NULL, + "valueA" DOUBLE PRECISION NOT NULL, + "valueB" DOUBLE PRECISION NOT NULL, + "sourceA" TEXT NOT NULL, + "sourceB" TEXT NOT NULL, + "diff" DOUBLE PRECISION NOT NULL, + "diffRatio" DOUBLE PRECISION NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Alert_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ServerNode" ( + "id" SERIAL NOT NULL, + "uuid" TEXT NOT NULL, + "name" TEXT NOT NULL, + "cpuName" TEXT NOT NULL DEFAULT '', + "os" TEXT NOT NULL DEFAULT '', + "region" TEXT NOT NULL DEFAULT '', + "memTotal" BIGINT NOT NULL DEFAULT 0, + "diskTotal" BIGINT NOT NULL DEFAULT 0, + "cpuCores" INTEGER NOT NULL DEFAULT 0, + "cpuUsage" DOUBLE PRECISION NOT NULL DEFAULT 0, + "ramUsed" BIGINT NOT NULL DEFAULT 0, + "swapUsed" BIGINT NOT NULL DEFAULT 0, + "load1" DOUBLE PRECISION NOT NULL DEFAULT 0, + "load5" DOUBLE PRECISION NOT NULL DEFAULT 0, + "load15" DOUBLE PRECISION NOT NULL DEFAULT 0, + "diskUsed" BIGINT NOT NULL DEFAULT 0, + "netIn" BIGINT NOT NULL DEFAULT 0, + "netOut" BIGINT NOT NULL DEFAULT 0, + "uptime" BIGINT NOT NULL DEFAULT 0, + "process" INTEGER NOT NULL DEFAULT 0, + "connections" INTEGER NOT NULL DEFAULT 0, + "status" TEXT NOT NULL DEFAULT 'unknown', + "recordedAt" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "ServerNode_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ServerNodeHistory" ( + "id" SERIAL NOT NULL, + "uuid" TEXT NOT NULL, + "period" TIMESTAMP(3) NOT NULL, + "cpu" DOUBLE PRECISION NOT NULL DEFAULT 0, + "ram" BIGINT NOT NULL DEFAULT 0, + "load" DOUBLE PRECISION NOT NULL DEFAULT 0, + "disk" BIGINT NOT NULL DEFAULT 0, + "netIn" BIGINT NOT NULL DEFAULT 0, + "netOut" BIGINT NOT NULL DEFAULT 0, + "process" INTEGER NOT NULL DEFAULT 0, + "connections" INTEGER NOT NULL DEFAULT 0, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "ServerNodeHistory_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ServiceCheck" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "url" TEXT NOT NULL, + "status" TEXT NOT NULL DEFAULT 'unknown', + "latencyMs" INTEGER NOT NULL DEFAULT 0, + "message" TEXT NOT NULL DEFAULT '', + "lastCheckAt" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "ServiceCheck_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Econ_SowInventory" ( + "id" SERIAL NOT NULL, + "month" TEXT NOT NULL, + "inventory" INTEGER NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Econ_SowInventory_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ScraperCache" ( + "id" SERIAL NOT NULL, + "scraperName" TEXT NOT NULL, + "lastDataHash" TEXT, + "lastUpdateAt" TIMESTAMP(3) NOT NULL, + "metadata" JSONB, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "ScraperCache_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "OilPriceHistory" ( + "id" SERIAL NOT NULL, + "date" TIMESTAMP(3) NOT NULL, + "province" TEXT NOT NULL, + "fuel" TEXT NOT NULL, + "price" DOUBLE PRECISION NOT NULL, + "source" TEXT NOT NULL DEFAULT 'apizero', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "OilPriceHistory_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "MihomeAccount" ( + "id" SERIAL NOT NULL, + "key" TEXT NOT NULL DEFAULT 'default', + "userId" TEXT NOT NULL DEFAULT '', + "status" TEXT NOT NULL DEFAULT 'unknown', + "lastError" TEXT NOT NULL DEFAULT '', + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "MihomeAccount_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "MihomeHome" ( + "id" SERIAL NOT NULL, + "homeId" TEXT NOT NULL, + "name" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "MihomeHome_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "MihomeDevice" ( + "id" SERIAL NOT NULL, + "did" TEXT NOT NULL, + "homeId" TEXT NOT NULL, + "model" TEXT NOT NULL, + "name" TEXT NOT NULL, + "alias" TEXT, + "tempSiid" INTEGER NOT NULL, + "tempPiid" INTEGER NOT NULL, + "humSiid" INTEGER NOT NULL, + "humPiid" INTEGER NOT NULL, + "battSiid" INTEGER NOT NULL, + "battPiid" INTEGER NOT NULL, + "battery" INTEGER, + "batteryAt" TIMESTAMP(3), + "enabled" BOOLEAN NOT NULL DEFAULT true, + "lastSyncAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "MihomeDevice_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "MihomeEnvSample" ( + "id" SERIAL NOT NULL, + "deviceId" INTEGER NOT NULL, + "date" TIMESTAMP(3) NOT NULL, + "temperature" DOUBLE PRECISION, + "humidity" DOUBLE PRECISION, + "temperatureAvg" DOUBLE PRECISION, + "humidityAvg" DOUBLE PRECISION, + "temperatureMin" DOUBLE PRECISION, + "temperatureMax" DOUBLE PRECISION, + "humidityMin" DOUBLE PRECISION, + "humidityMax" DOUBLE PRECISION, + "origin" TEXT NOT NULL DEFAULT 'live', + "sampledAt" TIMESTAMP(3), + "raw" JSONB, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "MihomeEnvSample_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "MihomeEnvHour" ( + "id" SERIAL NOT NULL, + "deviceId" INTEGER NOT NULL, + "hour" TIMESTAMP(3) NOT NULL, + "temperatureAvg" DOUBLE PRECISION, + "humidityAvg" DOUBLE PRECISION, + "raw" JSONB, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "MihomeEnvHour_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Habit" ( + "id" SERIAL NOT NULL, + "userId" INTEGER NOT NULL, + "name" TEXT NOT NULL, + "period" TEXT NOT NULL DEFAULT 'day', + "target" INTEGER NOT NULL DEFAULT 1, + "color" TEXT NOT NULL DEFAULT '#1677ff', + "icon" TEXT NOT NULL DEFAULT '', + "note" TEXT NOT NULL DEFAULT '', + "archived" BOOLEAN NOT NULL DEFAULT false, + "sortOrder" INTEGER NOT NULL DEFAULT 0, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Habit_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "CheckIn" ( + "id" SERIAL NOT NULL, + "habitId" INTEGER NOT NULL, + "userId" INTEGER NOT NULL, + "date" VARCHAR(10) NOT NULL, + "seq" INTEGER NOT NULL DEFAULT 1, + "note" TEXT NOT NULL DEFAULT '', + "source" TEXT NOT NULL DEFAULT 'user', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "CheckIn_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "CheckList" ( + "id" SERIAL NOT NULL, + "userId" INTEGER NOT NULL, + "title" TEXT NOT NULL, + "scene" TEXT NOT NULL DEFAULT '', + "note" TEXT NOT NULL DEFAULT '', + "archived" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "CheckList_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "CheckListItem" ( + "id" SERIAL NOT NULL, + "listId" INTEGER NOT NULL, + "title" TEXT NOT NULL, + "note" TEXT NOT NULL DEFAULT '', + "done" BOOLEAN NOT NULL DEFAULT false, + "doneAt" TIMESTAMP(3), + "sortOrder" INTEGER NOT NULL DEFAULT 0, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "CheckListItem_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "CheckListTemplate" ( + "id" SERIAL NOT NULL, + "userId" INTEGER, + "name" TEXT NOT NULL, + "scene" TEXT NOT NULL DEFAULT '', + "items" JSONB NOT NULL, + "builtin" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "CheckListTemplate_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Project" ( + "id" SERIAL NOT NULL, + "uuid" VARCHAR(36) NOT NULL, + "code" VARCHAR(8) NOT NULL, + "name" VARCHAR(50) NOT NULL, + "ownerId" INTEGER NOT NULL, + "isPersonal" BOOLEAN NOT NULL DEFAULT false, + "archived" BOOLEAN NOT NULL DEFAULT false, + "notes" JSONB NOT NULL DEFAULT '[]', + "taskKinds" JSONB, + "icon" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "Project_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ProjectMember" ( + "id" SERIAL NOT NULL, + "uuid" VARCHAR(36) NOT NULL, + "projectId" INTEGER NOT NULL, + "userId" INTEGER NOT NULL, + "permission" TEXT NOT NULL DEFAULT 'member', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "ProjectMember_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "BoardColumn" ( + "id" SERIAL NOT NULL, + "uuid" VARCHAR(36) NOT NULL, + "projectId" INTEGER NOT NULL, + "name" VARCHAR(20) NOT NULL, + "sortOrder" INTEGER NOT NULL DEFAULT 0, + "boundStatus" VARCHAR(8), + "retention" VARCHAR(8) NOT NULL DEFAULT 'forever', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "BoardColumn_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Task" ( + "id" SERIAL NOT NULL, + "uuid" VARCHAR(36) NOT NULL, + "projectId" INTEGER NOT NULL, + "columnId" INTEGER NOT NULL, + "parentId" INTEGER, + "title" VARCHAR(200) NOT NULL, + "description" TEXT NOT NULL DEFAULT '', + "kind" VARCHAR(20), + "markDate" TIMESTAMP(3), + "status" VARCHAR(8) NOT NULL DEFAULT 'todo', + "doneAt" TIMESTAMP(3), + "assigneeId" INTEGER, + "creatorId" INTEGER NOT NULL, + "extras" JSONB NOT NULL DEFAULT '[]', + "sortOrder" INTEGER NOT NULL DEFAULT 0, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "number" INTEGER, + + CONSTRAINT "Task_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "TaskMember" ( + "id" SERIAL NOT NULL, + "uuid" VARCHAR(36) NOT NULL, + "taskId" INTEGER NOT NULL, + "userId" INTEGER NOT NULL, + + CONSTRAINT "TaskMember_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "TaskTag" ( + "id" SERIAL NOT NULL, + "uuid" VARCHAR(36) NOT NULL, + "taskId" INTEGER NOT NULL, + "name" VARCHAR(20) NOT NULL, + + CONSTRAINT "TaskTag_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "Article_slug_key" ON "Article"("slug"); + +-- CreateIndex +CREATE UNIQUE INDEX "Indicator_name_key" ON "Indicator"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "IndicatorPoint_indicatorId_period_key" ON "IndicatorPoint"("indicatorId", "period"); + +-- CreateIndex +CREATE INDEX "News_publishedAt_idx" ON "News"("publishedAt"); + +-- CreateIndex +CREATE UNIQUE INDEX "News_title_publishedAt_key" ON "News"("title", "publishedAt"); + +-- CreateIndex +CREATE INDEX "Alert_createdAt_idx" ON "Alert"("createdAt"); + +-- CreateIndex +CREATE UNIQUE INDEX "ServerNode_uuid_key" ON "ServerNode"("uuid"); + +-- CreateIndex +CREATE INDEX "ServerNodeHistory_uuid_period_idx" ON "ServerNodeHistory"("uuid", "period"); + +-- CreateIndex +CREATE UNIQUE INDEX "ServerNodeHistory_uuid_period_key" ON "ServerNodeHistory"("uuid", "period"); + +-- CreateIndex +CREATE UNIQUE INDEX "ServiceCheck_name_key" ON "ServiceCheck"("name"); + +-- CreateIndex +CREATE UNIQUE INDEX "Econ_SowInventory_month_key" ON "Econ_SowInventory"("month"); + +-- CreateIndex +CREATE UNIQUE INDEX "ScraperCache_scraperName_key" ON "ScraperCache"("scraperName"); + +-- CreateIndex +CREATE INDEX "OilPriceHistory_province_fuel_date_idx" ON "OilPriceHistory"("province", "fuel", "date"); + +-- CreateIndex +CREATE UNIQUE INDEX "OilPriceHistory_date_province_fuel_key" ON "OilPriceHistory"("date", "province", "fuel"); + +-- CreateIndex +CREATE UNIQUE INDEX "MihomeAccount_key_key" ON "MihomeAccount"("key"); + +-- CreateIndex +CREATE UNIQUE INDEX "MihomeHome_homeId_key" ON "MihomeHome"("homeId"); + +-- CreateIndex +CREATE UNIQUE INDEX "MihomeDevice_did_key" ON "MihomeDevice"("did"); + +-- CreateIndex +CREATE INDEX "MihomeEnvSample_deviceId_date_idx" ON "MihomeEnvSample"("deviceId", "date"); + +-- CreateIndex +CREATE UNIQUE INDEX "MihomeEnvSample_deviceId_date_key" ON "MihomeEnvSample"("deviceId", "date"); + +-- CreateIndex +CREATE INDEX "MihomeEnvHour_deviceId_hour_idx" ON "MihomeEnvHour"("deviceId", "hour"); + +-- CreateIndex +CREATE UNIQUE INDEX "MihomeEnvHour_deviceId_hour_key" ON "MihomeEnvHour"("deviceId", "hour"); + +-- CreateIndex +CREATE INDEX "Habit_userId_archived_sortOrder_idx" ON "Habit"("userId", "archived", "sortOrder"); + +-- CreateIndex +CREATE INDEX "CheckIn_userId_date_idx" ON "CheckIn"("userId", "date"); + +-- CreateIndex +CREATE INDEX "CheckIn_habitId_date_idx" ON "CheckIn"("habitId", "date"); + +-- CreateIndex +CREATE UNIQUE INDEX "CheckIn_habitId_date_seq_key" ON "CheckIn"("habitId", "date", "seq"); + +-- CreateIndex +CREATE INDEX "CheckList_userId_archived_idx" ON "CheckList"("userId", "archived"); + +-- CreateIndex +CREATE INDEX "CheckListItem_listId_sortOrder_idx" ON "CheckListItem"("listId", "sortOrder"); + +-- CreateIndex +CREATE INDEX "CheckListTemplate_userId_builtin_idx" ON "CheckListTemplate"("userId", "builtin"); + +-- CreateIndex +CREATE UNIQUE INDEX "Project_uuid_key" ON "Project"("uuid"); + +-- CreateIndex +CREATE UNIQUE INDEX "Project_code_key" ON "Project"("code"); + +-- CreateIndex +CREATE INDEX "Project_ownerId_idx" ON "Project"("ownerId"); + +-- CreateIndex +CREATE INDEX "Project_archived_idx" ON "Project"("archived"); + +-- CreateIndex +CREATE UNIQUE INDEX "ProjectMember_uuid_key" ON "ProjectMember"("uuid"); + +-- CreateIndex +CREATE INDEX "ProjectMember_userId_idx" ON "ProjectMember"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "ProjectMember_projectId_userId_key" ON "ProjectMember"("projectId", "userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "BoardColumn_uuid_key" ON "BoardColumn"("uuid"); + +-- CreateIndex +CREATE INDEX "BoardColumn_projectId_sortOrder_idx" ON "BoardColumn"("projectId", "sortOrder"); + +-- CreateIndex +CREATE UNIQUE INDEX "BoardColumn_projectId_name_key" ON "BoardColumn"("projectId", "name"); + +-- CreateIndex +CREATE UNIQUE INDEX "Task_uuid_key" ON "Task"("uuid"); + +-- CreateIndex +CREATE INDEX "Task_projectId_columnId_idx" ON "Task"("projectId", "columnId"); + +-- CreateIndex +CREATE INDEX "Task_projectId_status_idx" ON "Task"("projectId", "status"); + +-- CreateIndex +CREATE INDEX "Task_parentId_idx" ON "Task"("parentId"); + +-- CreateIndex +CREATE INDEX "Task_projectId_doneAt_idx" ON "Task"("projectId", "doneAt"); + +-- CreateIndex +CREATE UNIQUE INDEX "Task_projectId_number_key" ON "Task"("projectId", "number"); + +-- CreateIndex +CREATE UNIQUE INDEX "TaskMember_uuid_key" ON "TaskMember"("uuid"); + +-- CreateIndex +CREATE UNIQUE INDEX "TaskMember_taskId_userId_key" ON "TaskMember"("taskId", "userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "TaskTag_uuid_key" ON "TaskTag"("uuid"); + +-- CreateIndex +CREATE INDEX "TaskTag_taskId_idx" ON "TaskTag"("taskId"); + +-- AddForeignKey +ALTER TABLE "IndicatorPoint" ADD CONSTRAINT "IndicatorPoint_indicatorId_fkey" FOREIGN KEY ("indicatorId") REFERENCES "Indicator"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "MihomeDevice" ADD CONSTRAINT "MihomeDevice_homeId_fkey" FOREIGN KEY ("homeId") REFERENCES "MihomeHome"("homeId") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "MihomeEnvSample" ADD CONSTRAINT "MihomeEnvSample_deviceId_fkey" FOREIGN KEY ("deviceId") REFERENCES "MihomeDevice"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "MihomeEnvHour" ADD CONSTRAINT "MihomeEnvHour_deviceId_fkey" FOREIGN KEY ("deviceId") REFERENCES "MihomeDevice"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Habit" ADD CONSTRAINT "Habit_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CheckIn" ADD CONSTRAINT "CheckIn_habitId_fkey" FOREIGN KEY ("habitId") REFERENCES "Habit"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CheckIn" ADD CONSTRAINT "CheckIn_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CheckList" ADD CONSTRAINT "CheckList_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CheckListItem" ADD CONSTRAINT "CheckListItem_listId_fkey" FOREIGN KEY ("listId") REFERENCES "CheckList"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CheckListTemplate" ADD CONSTRAINT "CheckListTemplate_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Project" ADD CONSTRAINT "Project_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ProjectMember" ADD CONSTRAINT "ProjectMember_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ProjectMember" ADD CONSTRAINT "ProjectMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "BoardColumn" ADD CONSTRAINT "BoardColumn_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Task" ADD CONSTRAINT "Task_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Task" ADD CONSTRAINT "Task_columnId_fkey" FOREIGN KEY ("columnId") REFERENCES "BoardColumn"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Task" ADD CONSTRAINT "Task_parentId_fkey" FOREIGN KEY ("parentId") REFERENCES "Task"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Task" ADD CONSTRAINT "Task_assigneeId_fkey" FOREIGN KEY ("assigneeId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Task" ADD CONSTRAINT "Task_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TaskMember" ADD CONSTRAINT "TaskMember_taskId_fkey" FOREIGN KEY ("taskId") REFERENCES "Task"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TaskMember" ADD CONSTRAINT "TaskMember_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TaskTag" ADD CONSTRAINT "TaskTag_taskId_fkey" FOREIGN KEY ("taskId") REFERENCES "Task"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20261026000000_task_archive/migration.sql b/prisma/migrations/20261026000000_task_archive/migration.sql new file mode 100644 index 0000000..fc8d9f4 --- /dev/null +++ b/prisma/migrations/20261026000000_task_archive/migration.sql @@ -0,0 +1,7 @@ +-- 任务归档 (T-ARC): 项目自动归档档位 + 任务归档三字段与查询索引 +ALTER TABLE `Project` ADD COLUMN `autoArchive` VARCHAR(8) NOT NULL DEFAULT 'never'; +ALTER TABLE `Task` ADD COLUMN `archived` BOOLEAN NOT NULL DEFAULT false; +ALTER TABLE `Task` ADD COLUMN `archivedAt` DATETIME(3) NULL; +ALTER TABLE `Task` ADD COLUMN `archiveSource` VARCHAR(8) NULL; +ALTER TABLE `Task` ADD INDEX `idx_task_project_archived` (`projectId`, `archived`, `archivedAt`); +ALTER TABLE `Task` ADD INDEX `idx_task_project_status_arch` (`projectId`, `status`, `archived`); diff --git a/prisma/migrations/20261026010000_uuid_fk_mapping/migration.sql b/prisma/migrations/20261026010000_uuid_fk_mapping/migration.sql new file mode 100644 index 0000000..21caf85 --- /dev/null +++ b/prisma/migrations/20261026010000_uuid_fk_mapping/migration.sql @@ -0,0 +1,96 @@ +-- uuid 外键镜像 (PowerSync Sequential ID Mapping, 见 docs/V0.2/离线编辑模块/0.2.1实施方案.md §3) +-- 目的: 同步流以 uuid 作为客户端主键 (离线新建行同步回填后本地 id 稳定不变), +-- 外键列同步输出 uuid 镜像 (别名回 projectId/columnId/parentId/taskId), 客户端零改动。 +-- 镜像列由触发器维护: REST/Prisma 写 int 外键 -> 触发器回填 uuid; upload 端在应用层解析 uuid -> int。 + +-- 1) 加列 (先可空) +ALTER TABLE "BoardColumn" ADD COLUMN "projectUuid" VARCHAR(36); +ALTER TABLE "Task" ADD COLUMN "projectUuid" VARCHAR(36), ADD COLUMN "columnUuid" VARCHAR(36), ADD COLUMN "parentUuid" VARCHAR(36); +ALTER TABLE "ProjectMember" ADD COLUMN "projectUuid" VARCHAR(36); +ALTER TABLE "TaskMember" ADD COLUMN "taskUuid" VARCHAR(36); +ALTER TABLE "TaskTag" ADD COLUMN "taskUuid" VARCHAR(36); + +-- 2) 存量回填 +UPDATE "BoardColumn" c SET "projectUuid" = p."uuid" FROM "Project" p WHERE c."projectId" = p."id"; +UPDATE "Task" t SET "projectUuid" = p."uuid" FROM "Project" p WHERE t."projectId" = p."id"; +UPDATE "Task" t SET "columnUuid" = c."uuid" FROM "BoardColumn" c WHERE t."columnId" = c."id"; +UPDATE "Task" t SET "parentUuid" = pt."uuid" FROM "Task" pt WHERE t."parentId" = pt."id"; +UPDATE "ProjectMember" pm SET "projectUuid" = p."uuid" FROM "Project" p WHERE pm."projectId" = p."id"; +UPDATE "TaskMember" tm SET "taskUuid" = t."uuid" FROM "Task" t WHERE tm."taskId" = t."id"; +UPDATE "TaskTag" tt SET "taskUuid" = t."uuid" FROM "Task" t WHERE tt."taskId" = t."id"; + +-- 3) 非空约束 +ALTER TABLE "BoardColumn" ALTER COLUMN "projectUuid" SET NOT NULL; +ALTER TABLE "Task" ALTER COLUMN "projectUuid" SET NOT NULL; +ALTER TABLE "Task" ALTER COLUMN "columnUuid" SET NOT NULL; +ALTER TABLE "ProjectMember" ALTER COLUMN "projectUuid" SET NOT NULL; +ALTER TABLE "TaskMember" ALTER COLUMN "taskUuid" SET NOT NULL; +ALTER TABLE "TaskTag" ALTER COLUMN "taskUuid" SET NOT NULL; + +-- 4) 外键 (级联与 int 外键一致) +ALTER TABLE "BoardColumn" ADD CONSTRAINT "BoardColumn_projectUuid_fkey" FOREIGN KEY ("projectUuid") REFERENCES "Project"("uuid") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "Task" ADD CONSTRAINT "Task_projectUuid_fkey" FOREIGN KEY ("projectUuid") REFERENCES "Project"("uuid") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "Task" ADD CONSTRAINT "Task_columnUuid_fkey" FOREIGN KEY ("columnUuid") REFERENCES "BoardColumn"("uuid") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "Task" ADD CONSTRAINT "Task_parentUuid_fkey" FOREIGN KEY ("parentUuid") REFERENCES "Task"("uuid") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "ProjectMember" ADD CONSTRAINT "ProjectMember_projectUuid_fkey" FOREIGN KEY ("projectUuid") REFERENCES "Project"("uuid") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "TaskMember" ADD CONSTRAINT "TaskMember_taskUuid_fkey" FOREIGN KEY ("taskUuid") REFERENCES "Task"("uuid") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "TaskTag" ADD CONSTRAINT "TaskTag_taskUuid_fkey" FOREIGN KEY ("taskUuid") REFERENCES "Task"("uuid") ON DELETE CASCADE ON UPDATE CASCADE; + +-- 5) 触发器: 写入时按 int 外键回填 uuid 镜像 +CREATE OR REPLACE FUNCTION "func_projectmember_uuid_map"() RETURNS TRIGGER AS $$ +BEGIN + SELECT "uuid" INTO NEW."projectUuid" FROM "Project" WHERE "id" = NEW."projectId"; + IF NOT FOUND THEN RAISE EXCEPTION 'Project % 不存在', NEW."projectId"; END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER "trg_projectmember_uuid_map" BEFORE INSERT OR UPDATE ON "ProjectMember" + FOR EACH ROW EXECUTE FUNCTION "func_projectmember_uuid_map"(); + +CREATE OR REPLACE FUNCTION "func_boardcolumn_uuid_map"() RETURNS TRIGGER AS $$ +BEGIN + SELECT "uuid" INTO NEW."projectUuid" FROM "Project" WHERE "id" = NEW."projectId"; + IF NOT FOUND THEN RAISE EXCEPTION 'Project % 不存在', NEW."projectId"; END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER "trg_boardcolumn_uuid_map" BEFORE INSERT OR UPDATE ON "BoardColumn" + FOR EACH ROW EXECUTE FUNCTION "func_boardcolumn_uuid_map"(); + +CREATE OR REPLACE FUNCTION "func_task_uuid_map"() RETURNS TRIGGER AS $$ +BEGIN + SELECT "uuid" INTO NEW."projectUuid" FROM "Project" WHERE "id" = NEW."projectId"; + IF NOT FOUND THEN RAISE EXCEPTION 'Project % 不存在', NEW."projectId"; END IF; + SELECT "uuid" INTO NEW."columnUuid" FROM "BoardColumn" WHERE "id" = NEW."columnId"; + IF NOT FOUND THEN RAISE EXCEPTION 'BoardColumn % 不存在', NEW."columnId"; END IF; + IF NEW."parentId" IS NULL THEN + NEW."parentUuid" := NULL; + ELSE + SELECT "uuid" INTO NEW."parentUuid" FROM "Task" WHERE "id" = NEW."parentId"; + IF NOT FOUND THEN RAISE EXCEPTION 'Task % 不存在', NEW."parentId"; END IF; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER "trg_task_uuid_map" BEFORE INSERT OR UPDATE ON "Task" + FOR EACH ROW EXECUTE FUNCTION "func_task_uuid_map"(); + +CREATE OR REPLACE FUNCTION "func_taskmember_uuid_map"() RETURNS TRIGGER AS $$ +BEGIN + SELECT "uuid" INTO NEW."taskUuid" FROM "Task" WHERE "id" = NEW."taskId"; + IF NOT FOUND THEN RAISE EXCEPTION 'Task % 不存在', NEW."taskId"; END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER "trg_taskmember_uuid_map" BEFORE INSERT OR UPDATE ON "TaskMember" + FOR EACH ROW EXECUTE FUNCTION "func_taskmember_uuid_map"(); + +CREATE OR REPLACE FUNCTION "func_tasktag_uuid_map"() RETURNS TRIGGER AS $$ +BEGIN + SELECT "uuid" INTO NEW."taskUuid" FROM "Task" WHERE "id" = NEW."taskId"; + IF NOT FOUND THEN RAISE EXCEPTION 'Task % 不存在', NEW."taskId"; END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +CREATE TRIGGER "trg_tasktag_uuid_map" BEFORE INSERT OR UPDATE ON "TaskTag" + FOR EACH ROW EXECUTE FUNCTION "func_tasktag_uuid_map"(); diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml index 592fc0b..044d57c 100644 --- a/prisma/migrations/migration_lock.toml +++ b/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually # It should be added in your version-control system (e.g., Git) -provider = "mysql" +provider = "postgresql" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index c045dd1..d8ef3c2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -9,7 +9,7 @@ generator client { } datasource db { - provider = "mysql" + provider = "postgresql" } model User { @@ -19,6 +19,19 @@ model User { password String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt + + // V0.2 打卡 / 清单: 用户删除时级联清理 + habits Habit[] + checkIns CheckIn[] + checkLists CheckList[] + templates CheckListTemplate[] + + // V0.2 任务模块 + ownedProjects Project[] @relation("ProjectOwner") + projectMembers ProjectMember[] + assignedTasks Task[] @relation("TaskAssignee") + createdTasks Task[] @relation("TaskCreator") + taskMembers TaskMember[] } model Article { @@ -281,4 +294,223 @@ model MihomeEnvHour { @@index([deviceId, hour]) } +// ==================== 打卡 / 日历 / 清单 (V0.2) ==================== +// 打卡项: 周期性目标 (每天 / 每周 / 每月 / 每年 n 次) +model Habit { + id Int @id @default(autoincrement()) + userId Int + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + name String + period String @default("day") // day | week | month | year + target Int @default(1) // 周期内目标次数 n (>=1) + color String @default("#1677ff") + icon String @default("") // emoji 或图标名, 预留 + note String @default("") + archived Boolean @default(false) // 归档: 不出现在今日待办, 历史保留 + sortOrder Int @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + records CheckIn[] + + @@index([userId, archived, sortOrder]) +} + +// 打卡记录: 一次打卡 = 一条; 同日多次 => seq 递增 (1..n, 超额继续累加) +model CheckIn { + id Int @id @default(autoincrement()) + habitId Int + habit Habit @relation(fields: [habitId], references: [id], onDelete: Cascade) + userId Int + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + date String @db.VarChar(10) // 本地日历日 "YYYY-MM-DD" (Asia/Shanghai) + seq Int @default(1) // 当日第几次 + note String @default("") + source String @default("user") // user | backfill (补打) + createdAt DateTime @default(now()) + + @@unique([habitId, date, seq]) + @@index([userId, date]) + @@index([habitId, date]) +} + +// 检查清单: 出差 / 回老家 等场景的一次性清单 +model CheckList { + id Int @id @default(autoincrement()) + userId Int + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + title String + scene String @default("") // 出差 | 回老家 | 自定义 + note String @default("") + archived Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + items CheckListItem[] + + @@index([userId, archived]) +} + +// 检查项: 勾选即完成 +model CheckListItem { + id Int @id @default(autoincrement()) + listId Int + list CheckList @relation(fields: [listId], references: [id], onDelete: Cascade) + title String + note String @default("") + done Boolean @default(false) + doneAt DateTime? + sortOrder Int @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([listId, sortOrder]) +} + +// 清单模板: builtin = 系统预置 (userId 为空); 否则为用户自定义 +model CheckListTemplate { + id Int @id @default(autoincrement()) + userId Int? + user User? @relation(fields: [userId], references: [id], onDelete: Cascade) + name String + scene String @default("") + items Json // [{ title, note }] + builtin Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([userId, builtin]) +} + +// ================== 任务模块 (V0.2 任务模块需求文档 T-*) ================== +// 口径见 docs/V0.2/任务模块/任务模块数据库设计文档.md +// 项目 = 隔离 + 权限单位; 看板列是实体(可增删改排序); +// 任务状态(todo/doing/done) 与 看板列 是两个正交维度, 由列的 boundStatus 决定是否联动. + +model Project { + id Int @id @default(autoincrement()) + uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑, 见 docs/V0.2/离线编辑模块) + code String @unique @db.VarChar(8) // 项目短编号: 用户自拟 2~8 位前缀, 全局唯一, 服务端分配 + name String @db.VarChar(50) + ownerId Int + owner User @relation("ProjectOwner", fields: [ownerId], references: [id], onDelete: Cascade) + isPersonal Boolean @default(false) + archived Boolean @default(false) + notes Json @default("[]") // 项目信息块 [{ title, content }] (T-PJ-13) + taskKinds Json? // 任务类型标签列表 (未配置时服务端回落默认四类) + icon String? @db.Text // 项目图标 (data URL, 客户端裁剪为 96x96 JPEG) + autoArchive String @default("never") @db.VarChar(8) // 自动归档档位: 3d/1w/1m/never (T-ARC-02) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + members ProjectMember[] + columns BoardColumn[] + tasks Task[] + + @@index([ownerId]) + @@index([archived]) +} + +model ProjectMember { + id Int @id @default(autoincrement()) + uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑) + projectUuid String? @db.VarChar(36) // 外键 uuid 镜像 (触发器维护, 库级 NOT NULL) + projectId Int + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + userId Int + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + permission String @default("member") + createdAt DateTime @default(now()) + + @@unique([projectId, userId]) + @@index([userId]) +} + +model BoardColumn { + id Int @id @default(autoincrement()) + uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑) + projectUuid String? @db.VarChar(36) // projectId 的 uuid 镜像 (触发器维护, 库级 NOT NULL; Prisma 侧可空以便触发器填充) + projectId Int + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + name String @db.VarChar(20) + sortOrder Int @default(0) + boundStatus String? @db.VarChar(8) + retention String @default("forever") @db.VarChar(8) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + tasks Task[] + + @@unique([projectId, name]) + @@index([projectId, sortOrder]) +} + +model Task { + id Int @id @default(autoincrement()) + uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑) + projectUuid String? @db.VarChar(36) // 外键 uuid 镜像 (触发器维护, 库级 NOT NULL) + columnUuid String? @db.VarChar(36) + parentUuid String? @db.VarChar(36) + projectId Int + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) + columnId Int + column BoardColumn @relation(fields: [columnId], references: [id], onDelete: Restrict) + parentId Int? + parent Task? @relation("Subtask", fields: [parentId], references: [id], onDelete: Cascade) + children Task[] @relation("Subtask") + + title String @db.VarChar(200) + description String @default("") @db.Text + kind String? @db.VarChar(20) // 任务类型标签 (项目 taskKinds 之一) + markDate DateTime? // 时间标记 (完整日期存储, 展示为 MM-dd) + status String @default("todo") @db.VarChar(8) + doneAt DateTime? + + assigneeId Int? + assignee User? @relation("TaskAssignee", fields: [assigneeId], references: [id], onDelete: SetNull) + creatorId Int + creator User @relation("TaskCreator", fields: [creatorId], references: [id], onDelete: Cascade) + + extras Json @default("[]") + sortOrder Int @default(0) + archived Boolean @default(false) + archivedAt DateTime? + archiveSource String? @db.VarChar(8) // manual | auto (T-ARC) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + members TaskMember[] + tags TaskTag[] + + number Int? // 项目内自增编号 (服务端分配, 展示为 `code-number`); 本地未定号的离线新建任务为空 + + @@unique([projectId, number]) + @@index([projectId, columnId]) + @@index([projectId, status]) + @@index([parentId]) + @@index([projectId, doneAt]) + @@index([projectId, archived, archivedAt]) + @@index([projectId, status, archived]) +} + +model TaskMember { + id Int @id @default(autoincrement()) + uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑) + taskUuid String? @db.VarChar(36) // 外键 uuid 镜像 (触发器维护, 库级 NOT NULL) + taskId Int + task Task @relation(fields: [taskId], references: [id], onDelete: Cascade) + userId Int + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([taskId, userId]) +} + +model TaskTag { + id Int @id @default(autoincrement()) + uuid String @unique @db.VarChar(36) // 同步标识: 客户端生成 (0.2.1 离线编辑) + taskUuid String? @db.VarChar(36) // 外键 uuid 镜像 (触发器维护, 库级 NOT NULL) + taskId Int + task Task @relation(fields: [taskId], references: [id], onDelete: Cascade) + name String @db.VarChar(20) + + @@index([taskId]) +} \ No newline at end of file diff --git a/public/dock.html b/public/dock.html new file mode 100644 index 0000000..31277d8 --- /dev/null +++ b/public/dock.html @@ -0,0 +1,34 @@ + + + + + + + + +
+ + + diff --git a/public/dock.js b/public/dock.js new file mode 100644 index 0000000..57b6a5a --- /dev/null +++ b/public/dock.js @@ -0,0 +1,65 @@ +// 悬浮球交互 (W-SNP-04/05/12/13/14): +// mouseenter/leave → hover 延时恢复 (Rust 侧计时 150ms) +// 单击 (位移 ≤4px) → 立即恢复, 触屏点按等效 +// 拖动 → dock_drag_move 增量移动, 结束时按最近边停靠/换边/拖出 +const { invoke } = window.__TAURI__.core +const ball = document.getElementById('ball') + +let dragging = false +let moved = 0 +let lastX = 0 +let lastY = 0 + +ball.addEventListener('mouseenter', () => invoke('dock_hover_start')) +ball.addEventListener('mouseleave', () => invoke('dock_hover_end')) +ball.addEventListener('contextmenu', (e) => e.preventDefault()) + +ball.addEventListener('pointerdown', (e) => { + dragging = true + moved = 0 + lastX = e.clientX + lastY = e.clientY + ball.setPointerCapture(e.pointerId) + invoke('dock_drag_start') +}) + +ball.addEventListener('pointermove', (e) => { + if (!dragging) return + const dx = e.clientX - lastX + const dy = e.clientY - lastY + if (dx === 0 && dy === 0) return + lastX = e.clientX + lastY = e.clientY + moved += Math.abs(dx) + Math.abs(dy) + if (moved > 4) { + // clientX 为逻辑像素, 增量按 DPI 缩放换算成物理像素 (Rust 注入 __SCALE) + const s = window.__SCALE || 1 + invoke('dock_drag_move', { dx: Math.round(dx * s), dy: Math.round(dy * s) }) + } +}) + +ball.addEventListener('pointerup', (e) => { + if (!dragging) return + dragging = false + try { ball.releasePointerCapture(e.pointerId) } catch { /* 已释放 */ } + if (moved <= 4) { + invoke('dock_click') + } else { + invoke('dock_drag_end') + } +}) + +ball.addEventListener('pointercancel', () => { + dragging = false + invoke('dock_drag_end') +}) + +// Rust 在停靠/换边/主题变化时调用 +window.applyDock = (edge, theme) => { + document.body.className = 'dock-' + edge + ' theme-' + theme +} +window.applyTheme = (theme) => { + const edge = [...document.body.classList].find((c) => c.startsWith('dock-')) || 'dock-left' + document.body.className = edge + ' theme-' + theme +} +window.__SCALE = 1 diff --git a/readme b/readme deleted file mode 100644 index 47f8653..0000000 --- a/readme +++ /dev/null @@ -1,114 +0,0 @@ -# iBoard -用于收集和整理经济数据,但是以策略为主体重新组织。 -- 首页主要是放经济文章 -- 数据主要是放各种经济数据和图表。 -- 策略是放按照经济理论组织的相关数据和图表的对比。 - - - -## 首页 -首页显示新闻模块和报警模块的数据,按照时间最新优先,无限滚动,直到数据库中没有数据为止。 - - -### 新闻模块 -增加一个新闻模块,数据来源是每日夜间 3点,通过妙想 MCP 获取国内外最新经济政治新闻,按影响大小排序10条。 -存入数据库。 - - -### 报警模块 -World Bank 中的数据只用来对比妙想 MCP 拉取的数据正确性。当同一时间的同一项数据不同时,记录一个报警。 - -## 监控 -监控分为服务器监控和服务监控两部分。 -服务器监控对接 komari,地址:https://komari.honor3.com/ - - - -## 前端 OTA 升级 - -只升级前端资源 (`dist/`), **不触碰** Tauri 框架/原生层, 不重打 APK. -底层使用 [`tauri-plugin-hotswap`](https://github.com/denniskribl/tauri-plugin-hotswap) -(签名/回滚/资源替换全部由插件处理). - -### 工作流 - -``` -开发者 服务端 -npm run build ──┐ -scripts/build-ota.mjs │ 生成 iboard-ota.tar.gz + minisign 签名 - │ 输出 latest.json (sequence/version/...) - ▼ - ota-package/ ── 部署到服务器 ──▶ /srv/iboard/ota-package - │ - ▼ - GET /api/ota/check/ -客户端 (Tauri) │ -启动 ─▶ notifyReady() ▼ -我页 ─▶ checkUpdate() ◀──── 200 + manifest (含 url/signature) ─────┘ - applyUpdate() ─▶ 下载 tar.gz ─▶ minisign 校验 ─▶ 解压到 AppData/ota-dist - location.reload() ─▶ 后续资源从 tauri://localhost 优先命中覆盖包 -``` - -### 首次部署 - -1. **生成签名密钥** (一次性): - ```bash - # 安装 minisign: Windows (scoop) / macOS (brew) / Linux (apt) - node scripts/ota-genkey.mjs - # 产物: ota-package/hotswap.{key, pub} - ``` -2. **把公钥写入 `src-tauri/tauri.conf.json`** 的 `plugins.hotswap.pubkey` 字段 - (脚本会打印). -3. **配置 endpoint** (`src-tauri/tauri.conf.json`): - ```json - "hotswap": { - "endpoint": "https://your-server/api/ota/check/{{current_sequence}}", - "pubkey": "RWR...", - "require_https": true - } - ``` -4. **服务端环境变量** `OTA_PACKAGE_DIR` 指向 `ota-package/` 所在目录. - -### 发布新版前端 - -```bash -npm run build # 生成 dist/ -node scripts/build-ota.mjs # 打成 tar.gz + 签名 + latest.json -# sequence 默认 = Date.now(), 也可 --sequence 43 手动指定 -# 也可加 --notes "修复登录按钮" --min-binary 0.1.0 -``` - -然后把 `ota-package/` 下两个文件 (`iboard-ota.tar.gz` + `latest.json`) 同步到 -服务端 `OTA_PACKAGE_DIR` 指向的目录. sequence 单调递增即可. - -### 客户端使用 - -打开 App 的"我"页, 点 [检查更新] → [立即升级] 即可. 失败会自动回滚到内置 dist. - -> **注意:** Tauri 2 的 dev 模式 (`tauri dev`) 会代理到 Vite dev server, **OTA 不生效**. -> OTA 仅在 release/打包构建中可用, 测试请用 `tauri build` 产物. - -### Web 端 - -Web 浏览器访问 (同源) 时, 服务端返回简化版 manifest, 仅显示"有新版本请刷新". -Web 端不会自动下载包, 也不会替换前端代码. - - -### CI 自动签名 (Gitea Actions) - -.gitea/workflows/ota-build.yml 是手动触发的 OTA 打包工作流 (每次发版前点一次). - -**配置 secret** (Gitea 仓库 -> Settings -> Actions -> Secrets): -- MINISIGN_PRIVATE_KEY: 完整 .key 文件内容, **整段** (含首行 `untrusted comment: minisign encrypted secret key` 和 base64 行), 整段作为多行 secret 粘贴即可. - -**使用流程**: -1. 在 Gitea Actions 页面选 OTA Frontend Build & Sign -> Run workflow -2. 填 sequence (可选, 不填用时间戳) 和 -otes (可选) -3. CI 跑完会在 artifact 里看到 ota-package/iboard-ota.tar.gz + latest.json + iboard-ota.tar.gz.minisig -4. 下载 artifact, scp 到服务器 OTA_PACKAGE_DIR 目录 -5. 客户端"我"页 [检查更新] 即可触发升级 - -**注意**: -- CI 用的私钥必须**与** auri.conf.json 里 plugins.hotswap.pubkey **配对**; 换密钥时需要同时更新 CI secret 和 conf. -- 不要把私钥 commit 到 git, 整个 ota-package/hotswap.key 已被 .gitignore 排除. diff --git a/scripts/build-ota.mjs b/scripts/build-ota.mjs index 15fd6f9..3eab17e 100644 --- a/scripts/build-ota.mjs +++ b/scripts/build-ota.mjs @@ -1,147 +1,157 @@ -#!/usr/bin/env node -// OTA 升级包生成脚本 (兼容 tauri-plugin-hotswap) -// 用法: -// node scripts/build-ota.mjs [--out ] [--sequence ] [--min-binary ] [--notes ] [--no-sign] [--key ] -// 流程: -// 1) 校验 dist/ 已构建 -// 2) 生成 bundle.tar.gz (dist/ 内容) -// 3) minisign 签名 (除非 --no-sign; 私钥优先读 MINISIGN_PRIVATE_KEY 环境变量) -// 4) 写 latest.json (manifest, 含 sequence/version/signature/bundle_size/notes/...) -// 5) 全部产物输出到 out 目录 (默认 ota-package/) -// -// minisign 安装: 见 readme "OTA 升级" 章节 -import fs from 'node:fs' -import path from 'node:path' -import crypto from 'node:crypto' -import { fileURLToPath } from 'node:url' -import { spawnSync } from 'node:child_process' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) -const ROOT = path.resolve(__dirname, '..') - -const args = process.argv.slice(2) -function getArg(name, def) { - const i = args.indexOf(name) - return i >= 0 && args[i + 1] ? args[i + 1] : def -} -const OUT_DIR = path.resolve(getArg('--out', 'ota-package')) -const SEQUENCE = Number(getArg('--sequence', String(Date.now()))) // 默认用时间戳做单调计数 -const MIN_BINARY = getArg('--min-binary', '') -const NOTES = getArg('--notes', '') -const NO_SIGN = args.includes('--no-sign') -// 私钥优先来源: MINISIGN_PRIVATE_KEY 环境变量 (CI 注入) → 文件 (--key 路径) -const KEY_PATH = path.resolve(getArg('--key', path.join(OUT_DIR, 'hotswap.key'))) -const KEY_FROM_ENV = !!process.env.MINISIGN_PRIVATE_KEY -// 旧版 tauri.conf.json 里的 pubkey (自动同步过去, 方便用户复制) -const TAURI_CONF = path.join(ROOT, 'src-tauri', 'tauri.conf.json') - -const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8')) -const APP_VERSION = pkg.version || '0.0.0' - -const DIST = path.join(ROOT, 'dist') -if (!fs.existsSync(path.join(DIST, 'index.html'))) { - console.error('[build-ota] 错误: dist/index.html 不存在, 请先执行 npm run build') - process.exit(1) -} - -// 准备输出目录 -fs.mkdirSync(OUT_DIR, { recursive: true }) - -// 1) 生成 bundle.tar.gz -const BUNDLE_NAME = 'iboard-ota.tar.gz' -const BUNDLE_PATH = path.join(OUT_DIR, BUNDLE_NAME) -const r = spawnSync('tar', ['-czf', BUNDLE_PATH, '-C', DIST, '.'], { stdio: 'inherit' }) -if (r.status !== 0) { - console.error('[build-ota] tar 打包失败') - process.exit(1) -} -const bundleStat = fs.statSync(BUNDLE_PATH) -const bundleSha256 = crypto.createHash('sha256').update(fs.readFileSync(BUNDLE_PATH)).digest('hex') - -// 2) 签名 (minisign) -let signature = '' -let pubkey = '' -if (!NO_SIGN) { - // CI 注入: 私钥走环境变量, 写到临时文件, 签名后立即删除 (避免落盘) - let KEY_PATH_TMP = null - let KEY_PATH_USE = KEY_PATH - if (KEY_FROM_ENV) { - KEY_PATH_TMP = path.join(OUT_DIR, `.hotswap.key.${process.pid}`) - fs.writeFileSync(KEY_PATH_TMP, process.env.MINISIGN_PRIVATE_KEY, { mode: 0o600 }) - KEY_PATH_USE = KEY_PATH_TMP - } - if (!fs.existsSync(KEY_PATH_USE)) { - console.error('[build-ota] 私钥不存在:', KEY_PATH_USE) - console.error(' 注入方式二选一:') - console.error(' 1) CI: 设环境变量 MINISIGN_PRIVATE_KEY (整段 .key 文件内容, 含首行 untrusted comment)') - console.error(' 2) 本机: 放 ' + KEY_PATH + ', 或用 --key ') - console.error(' 生成方法: minisign -G -W -p ota-package/hotswap.pub -s ota-package/hotswap.key') - process.exit(1) - } - // minisign -Sm -s 生成 detached signature - const sigArgs = ['-Sm', BUNDLE_PATH, '-s', KEY_PATH_USE] - const sigR = spawnSync('minisign', sigArgs, { stdio: 'inherit' }) - // 清理 CI 临时私钥 (无论成功失败都删) - if (KEY_PATH_TMP) { - try { fs.unlinkSync(KEY_PATH_TMP) } catch { /* ignore */ } - KEY_PATH_TMP = null - } - if (sigR.status !== 0) { - console.error('[build-ota] minisign 签名失败') - process.exit(1) - } - const sigPath = BUNDLE_PATH + '.minisig' - signature = fs.readFileSync(sigPath, 'utf8').trim() - - // 尝试同步 pubkey 到 tauri.conf.json (如果存在) - const PUB_PATH = path.join(path.dirname(KEY_PATH), 'hotswap.pub') - if (fs.existsSync(PUB_PATH)) { - pubkey = fs.readFileSync(PUB_PATH, 'utf8').trim() - const lines = pubkey.split(/\r?\n/) - pubkey = (lines.find((l) => l.startsWith('RWR') || l.startsWith('RWT') || l.startsWith('RWQ')) || '').trim() - } -} - -// 3) 写 latest.json -const minBinary = MIN_BINARY || (() => { - // 默认从 src-tauri/tauri.conf.json 读 version - try { return JSON.parse(fs.readFileSync(TAURI_CONF, 'utf8')).version || APP_VERSION } - catch { return APP_VERSION } -})() -const manifest = { - version: APP_VERSION + '-ota', - sequence: SEQUENCE, - min_binary_version: minBinary, - bundle: BUNDLE_NAME, - bundle_size: bundleStat.size, - bundle_sha256: bundleSha256, - signature, - notes: NOTES, - pub_date: new Date().toISOString(), - mandatory: false, - // 内部用: 前端 web 模式比对 build_id - build_id: 'seq-' + SEQUENCE, -} -fs.writeFileSync(path.join(OUT_DIR, 'latest.json'), JSON.stringify(manifest, null, 2)) - -// 4) 提示 -console.log('') -console.log('=== OTA 包生成完成 ===') -console.log('输出目录: ', OUT_DIR) -console.log('版本: ', manifest.version) -console.log('sequence: ', manifest.sequence) -console.log('bundle 大小: ', (bundleStat.size / 1024).toFixed(1) + ' KB') -console.log('bundle sha256:', bundleSha256) -console.log('签名: ', signature ? '(已生成, 写入 latest.json)' : '(未签名, --no-sign)') -if (pubkey) { - console.log('') - console.log('请将下面这一行 pubkey 写到 src-tauri/tauri.conf.json -> plugins.hotswap.pubkey:') - console.log(' ' + pubkey) -} - -console.log('') -console.log('下一步: 把这两个文件发布到 OTA 存储:') -console.log(' ' + BUNDLE_NAME) -console.log(' latest.json') -console.log('并确保 server 进程环境变量 OTA_PACKAGE_DIR 指向该目录。') +#!/usr/bin/env node +// OTA 升级包生成脚本 (兼容 tauri-plugin-hotswap) +// 用法: +// node scripts/build-ota.mjs [--out ] [--sequence ] [--min-binary ] [--notes ] [--no-sign] [--key ] +// 流程: +// 1) 校验 dist/ 已构建 +// 2) 生成 bundle.tar.gz (dist/ 内容) +// 3) minisign 签名 (除非 --no-sign; 私钥优先读 MINISIGN_PRIVATE_KEY 环境变量) +// 4) 写 latest.json (manifest, 含 sequence/version/signature/bundle_size/notes/...) +// 5) 全部产物输出到 out 目录 (默认 ota-package/) +// +// minisign 安装: 见 readme "OTA 升级" 章节 +import fs from 'node:fs' +import path from 'node:path' +import crypto from 'node:crypto' +import { fileURLToPath } from 'node:url' +import { spawnSync } from 'node:child_process' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const ROOT = path.resolve(__dirname, '..') + +const args = process.argv.slice(2) +function getArg(name, def) { + const i = args.indexOf(name) + return i >= 0 && args[i + 1] ? args[i + 1] : def +} +const OUT_DIR = path.resolve(getArg('--out', 'ota-package')) +const SEQUENCE = Number(getArg('--sequence', String(Date.now()))) // 默认用时间戳做单调计数 +const MIN_BINARY = getArg('--min-binary', '') +const NOTES = getArg('--notes', '') +// 构建对应的 git commit sha (发布说明=commit message, sha 单独一行展示) +const COMMIT = getArg('--commit', '') +const NO_SIGN = args.includes('--no-sign') +// 私钥优先来源: MINISIGN_PRIVATE_KEY 环境变量 (CI 注入) → 文件 (--key 路径) +const KEY_PATH = path.resolve(getArg('--key', path.join(OUT_DIR, 'hotswap.key'))) +const KEY_FROM_ENV = !!process.env.MINISIGN_PRIVATE_KEY +// 旧版 tauri.conf.json 里的 pubkey (自动同步过去, 方便用户复制) +const TAURI_CONF = path.join(ROOT, 'src-tauri', 'tauri.conf.json') + +const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8')) +const APP_VERSION = pkg.version || '0.0.0' + +const DIST = path.join(ROOT, 'dist') +if (!fs.existsSync(path.join(DIST, 'index.html'))) { + console.error('[build-ota] 错误: dist/index.html 不存在, 请先执行 npm run build') + process.exit(1) +} + +// 准备输出目录 +fs.mkdirSync(OUT_DIR, { recursive: true }) + +// 1) 生成 bundle.tar.gz +const BUNDLE_NAME = 'iboard-ota.tar.gz' +const BUNDLE_PATH = path.join(OUT_DIR, BUNDLE_NAME) +const r = spawnSync('tar', ['-czf', BUNDLE_PATH, '-C', DIST, '.'], { stdio: 'inherit' }) +if (r.status !== 0) { + console.error('[build-ota] tar 打包失败') + process.exit(1) +} +const bundleStat = fs.statSync(BUNDLE_PATH) +const bundleSha256 = crypto.createHash('sha256').update(fs.readFileSync(BUNDLE_PATH)).digest('hex') + +// 2) 签名 (minisign) +let signature = '' +let pubkey = '' +if (!NO_SIGN) { + // CI 注入: 私钥走环境变量, 写到临时文件, 签名后立即删除 (避免落盘) + let KEY_PATH_TMP = null + let KEY_PATH_USE = KEY_PATH + if (KEY_FROM_ENV) { + KEY_PATH_TMP = path.join(OUT_DIR, `.hotswap.key.${process.pid}`) + fs.writeFileSync(KEY_PATH_TMP, process.env.MINISIGN_PRIVATE_KEY, { mode: 0o600 }) + KEY_PATH_USE = KEY_PATH_TMP + } + if (!fs.existsSync(KEY_PATH_USE)) { + console.error('[build-ota] 私钥不存在:', KEY_PATH_USE) + console.error(' 注入方式二选一:') + console.error(' 1) CI: 设环境变量 MINISIGN_PRIVATE_KEY (整段 .key 文件内容, 含首行 untrusted comment)') + console.error(' 2) 本机: 放 ' + KEY_PATH + ', 或用 --key ') + console.error(' 生成方法: minisign -G -W -p ota-package/hotswap.pub -s ota-package/hotswap.key') + process.exit(1) + } + // minisign -Sm -s 生成 detached signature + const sigArgs = ['-Sm', BUNDLE_PATH, '-s', KEY_PATH_USE] + const sigR = spawnSync('minisign', sigArgs, { stdio: 'inherit' }) + // 清理 CI 临时私钥 (无论成功失败都删) + if (KEY_PATH_TMP) { + try { fs.unlinkSync(KEY_PATH_TMP) } catch { /* ignore */ } + KEY_PATH_TMP = null + } + if (sigR.status !== 0) { + console.error('[build-ota] minisign 签名失败') + process.exit(1) + } + const sigPath = BUNDLE_PATH + '.minisig' + signature = fs.readFileSync(sigPath, 'utf8').trim() + + // 尝试同步 pubkey 到 tauri.conf.json (如果存在) + const PUB_PATH = path.join(path.dirname(KEY_PATH), 'hotswap.pub') + if (fs.existsSync(PUB_PATH)) { + pubkey = fs.readFileSync(PUB_PATH, 'utf8').trim() + const lines = pubkey.split(/\r?\n/) + pubkey = (lines.find((l) => l.startsWith('RWR') || l.startsWith('RWT') || l.startsWith('RWQ')) || '').trim() + } +} + +// 3) 写 latest.json +const minBinary = MIN_BINARY || (() => { + // 默认从 src-tauri/tauri.conf.json 读 version + try { return JSON.parse(fs.readFileSync(TAURI_CONF, 'utf8')).version || APP_VERSION } + catch { return APP_VERSION } +})() +const manifest = { + version: APP_VERSION + '-ota', + sequence: SEQUENCE, + min_binary_version: minBinary, + bundle: BUNDLE_NAME, + bundle_size: bundleStat.size, + bundle_sha256: bundleSha256, + signature, + notes: String(NOTES).slice(0, 400), + commit: COMMIT || undefined, + pub_date: new Date().toISOString(), + mandatory: false, + // 内部用: 前端 web 模式比对 build_id。 + // 格式与前端 vite BUILD_ID 一致 (YYYYMMDDHHmmss, 北京时间); sequence 保持毫秒单调递增 + build_id: (() => { + const d = new Date(Date.now() + 8 * 3600 * 1000) + const p = (n, w) => String(n).padStart(w || 2, '0') + return `${d.getUTCFullYear()}${p(d.getUTCMonth() + 1)}${p(d.getUTCDate())}${p(d.getUTCHours())}${p(d.getUTCMinutes())}${p(d.getUTCSeconds())}` + })(), +} +fs.writeFileSync(path.join(OUT_DIR, 'latest.json'), JSON.stringify(manifest, null, 2)) + +// 4) 提示 +console.log('') +console.log('=== OTA 包生成完成 ===') +console.log('输出目录: ', OUT_DIR) +console.log('版本: ', manifest.version) +console.log('sequence: ', manifest.sequence) +console.log('build_id: ', manifest.build_id) +console.log('commit: ', manifest.commit || '(未指定)') +console.log('bundle 大小: ', (bundleStat.size / 1024).toFixed(1) + ' KB') +console.log('bundle sha256:', bundleSha256) +console.log('签名: ', signature ? '(已生成, 写入 latest.json)' : '(未签名, --no-sign)') +if (pubkey) { + console.log('') + console.log('请将下面这一行 pubkey 写到 src-tauri/tauri.conf.json -> plugins.hotswap.pubkey:') + console.log(' ' + pubkey) +} + +console.log('') +console.log('下一步: 把这两个文件发布到 OTA 存储:') +console.log(' ' + BUNDLE_NAME) +console.log(' latest.json') +console.log('并确保 server 进程环境变量 OTA_PACKAGE_DIR 指向该目录。') diff --git a/scripts/build-windows.mjs b/scripts/build-windows.mjs new file mode 100644 index 0000000..2ece216 --- /dev/null +++ b/scripts/build-windows.mjs @@ -0,0 +1,152 @@ +#!/usr/bin/env node +// Windows 外壳一键构建 (开发用): 安装包 (NSIS) + 便携版 (portable zip) +// +// 用法: +// npm run build:win # 完整流程 +// node scripts/build-windows.mjs --skip-check # 跳过版本一致性校验 +// node scripts/build-windows.mjs --debug # debug 构建 (快速验证, 产物带 -debug 后缀) +// +// 流程: +// 1) 版本一致性校验 (scripts/ci/check-version.mjs, 外壳三件套) +// 2) 前端构建 (build:shell, 注入 VITE_API_BASE 指向远端后端) +// 3) tauri build --bundles nsis → 编译一次 release exe + NSIS 安装包 +// 4) 复用同一个 exe 组装便携版目录并压成 zip (不二次编译) +// +// 产物 (artifacts/windows/): +// iBoard__x64-setup.exe 安装包 (当前用户安装, 免管理员) +// iBoard__x64-portable.zip 便携版 (解压即用) +// portable/ 便携版解压目录 (iBoard.exe + README.txt) +// +// 说明: 便携版不内置 WebView2 —— 目标机需 Win10 19041+ / Win11 (系统自带 WebView2 Runtime)。 +import { spawnSync } from 'node:child_process' +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const args = process.argv.slice(2) +const skipCheck = args.includes('--skip-check') +const isDebug = args.includes('--debug') +const profile = isDebug ? 'debug' : 'release' + +const conf = JSON.parse(fs.readFileSync(path.join(root, 'src-tauri', 'tauri.conf.json'), 'utf8')) +const version = String(conf.version || '0.0.0') +const product = conf.productName || 'iBoard' +const suffix = isDebug ? '-debug' : '' + +const artifactsDir = path.join(root, 'artifacts', 'windows') +const targetDir = path.join(root, 'src-tauri', 'target', profile) + +function log(msg) { + console.log(`\x1b[36m[build-win]\x1b[0m ${msg}`) +} +function fail(msg) { + console.error(`\x1b[31m[build-win] 失败: ${msg}\x1b[0m`) + process.exit(1) +} +function run(cmd, cmdArgs, opts = {}) { + log(`$ ${cmd} ${cmdArgs.join(' ')}`) + const res = spawnSync(cmd, cmdArgs, { + cwd: root, + stdio: 'inherit', + shell: process.platform === 'win32', + ...opts, + }) + if (res.status !== 0) fail(`${cmd} ${cmdArgs.join(' ')} 退出码 ${res.status}`) +} + +// ---------- 0. 前置检查 ---------- +if (process.platform !== 'win32') fail('本脚本仅支持 Windows') +if (!fs.existsSync(path.join(root, 'node_modules'))) fail('缺少 node_modules, 请先运行 npm ci') + +log(`项目 ${product} 外壳版本 ${version} (${profile})`) + +// ---------- 1. 版本一致性 ---------- +if (!skipCheck) { + log('校验版本一致性 (外壳 tauri.conf.json / Cargo.toml / Cargo.lock)') + run('node', ['scripts/ci/check-version.mjs'], { + env: { ...process.env, BRANCH: process.env.BRANCH || '' }, + }) +} else { + log('跳过版本一致性校验 (--skip-check)') +} + +// ---------- 2. 前端构建 ---------- +log('构建前端 (build:shell, VITE_API_BASE 指向远端后端)') +run('npm', ['run', 'build:shell']) + +// ---------- 3. 安装包 (NSIS) ---------- +const tauriArgs = ['tauri', 'build'] +if (isDebug) tauriArgs.push('--debug') +// 只出 NSIS: 避免 bundle.targets="all" 在 Windows 上拉 WiX 打 MSI +tauriArgs.push('--bundles', 'nsis') +log('编译外壳并生成 NSIS 安装包') +run('npx', tauriArgs) + +// ---------- 4. 便携版 ---------- +const exePath = path.join(targetDir, `iboard${suffix}.exe`) +if (!fs.existsSync(exePath)) fail(`未找到构建产物 ${exePath}`) + +fs.rmSync(artifactsDir, { recursive: true, force: true }) +fs.mkdirSync(artifactsDir, { recursive: true }) + +const portableDir = path.join(artifactsDir, 'portable') +fs.mkdirSync(portableDir, { recursive: true }) +const portableExe = path.join(portableDir, `${product}.exe`) +fs.copyFileSync(exePath, portableExe) + +const readme = `${product} ${version} 便携版 (Windows x64) +================================================= + +免安装: 双击 ${product}.exe 即可运行。 + +系统要求 + - Windows 10 19041+ 或 Windows 11 (x64) + - WebView2 Runtime: Win10/11 系统自带; 若提示缺失, 请安装 + https://developer.microsoft.com/microsoft-edge/webview2/ + +使用说明 + - 关闭窗口 = 隐藏到托盘, 不会退出; 退出请用托盘右键菜单「退出」。 + - 窗口贴屏幕边缘会自动收起为半圆悬浮球, 鼠标停留或单击唤回。 + - 托盘菜单可开关「开机启动」「边缘吸附」。 + - 配置保存在 %APPDATA%\\com.iboard.app\\shell-config.json (删除即恢复默认)。 + +注意: 便携版不写注册表、不装服务; 但若开启了「开机启动」, 会在 + HKCU\\...\\Run 写入自启动项, 关闭开关即可移除。 +` +fs.writeFileSync(path.join(portableDir, 'README.txt'), readme, 'utf8') + +const zipName = `${product}_${version}_x64-portable${suffix}.zip` +const zipPath = path.join(artifactsDir, zipName) +log('打包便携版 zip') +const psZip = `Compress-Archive -Path '${portableDir}\\*' -DestinationPath '${zipPath}' -Force` +const res = spawnSync('powershell', ['-NoProfile', '-NonInteractive', '-Command', psZip], { + cwd: root, + stdio: 'inherit', +}) +if (res.status !== 0) fail('压缩便携版失败 (Compress-Archive)') + +// ---------- 5. 汇总 ---------- +const setupExe = path.join( + targetDir, + 'bundle', + 'nsis', + `${product}_${version}_x64-setup${suffix}.exe`, +) +const outputs = [] +if (fs.existsSync(setupExe)) { + const dest = path.join(artifactsDir, path.basename(setupExe)) + fs.copyFileSync(setupExe, dest) + outputs.push(['安装包 (NSIS)', dest]) +} else { + outputs.push(['安装包 (NSIS)', `未找到: ${setupExe}`]) +} +outputs.push(['便携版 (zip)', zipPath]) +outputs.push(['便携版 (目录)', `${portableDir} (${fs.readdirSync(portableDir).length} 个文件)`]) + +log('构建完成:') +for (const [label, p] of outputs) { + const size = fs.existsSync(p) ? `${(fs.statSync(p).size / 1024 / 1024).toFixed(1)} MB` : '' + console.log(` ${label.padEnd(14)} ${p}${size ? ' (' + size + ')' : ''}`) +} +process.exit(0) diff --git a/scripts/ci/check-version.mjs b/scripts/ci/check-version.mjs new file mode 100644 index 0000000..0a45cf8 --- /dev/null +++ b/scripts/ci/check-version.mjs @@ -0,0 +1,120 @@ +#!/usr/bin/env node +// CI 专用: 校验版本号分组一致 + 前端版本与分支名对应 +// +// 背景 (2026-10-25 调整: 外壳与前端版本号解绑): +// 前端 (Web/OTA) 与外壳 (Android/Windows 壳) 独立维护、独立发版, +// 两组版本号不再要求相等, 只要求**组内**一致: +// +// 组1 前端: package.json .version +// ├─ 前端运行时 APP_VERSION = (裸, 无后缀) +// └─ OTA 热更包 version = -ota (打包脚本追加) +// +// 组2 外壳: src-tauri/tauri.conf.json .version (= APK versionName) +// ├─ src-tauri/Cargo.toml [package].version +// └─ src-tauri/Cargo.lock (iboard 条目) +// 外壳随 android-*/桌面发布独立演进, 与 package.json 无关。 +// +// 另: package.json 的 version 不允许带 "-ota" (OTA 后缀由打包脚本追加)。 +// +// 校验分两段: +// A. 组内一致性 (任何分支都查) +// B. 分支对应 (仅 dev-<版本> 分支, 针对前端组): +// dev-. → version 必须以 . 开头 +// dev- → version 必须以 . 开头 +// main / master / 其它 → 跳过 +// +// 环境变量: +// GITHUB_REF_NAME - Gitea Actions 提供的分支名 (本地运行可用 BRANCH 覆盖) +// +// 退出码: 0 通过 / 1 不一致 +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const ROOT = path.resolve(__dirname, '..', '..') + +const branch = process.env.GITHUB_REF_NAME || process.env.BRANCH || '' + +function readJson(rel) { + return JSON.parse(fs.readFileSync(path.join(ROOT, rel), 'utf8')) +} + +// Cargo.toml 的 [package].version 位于文件开头, 取第一个行首 version = "x" +function readCargoTomlVersion() { + const text = fs.readFileSync(path.join(ROOT, 'src-tauri/Cargo.toml'), 'utf8') + const m = /^version\s*=\s*"([^"]+)"/m.exec(text) + return m ? m[1] : null +} + +// Cargo.lock 里挑出 name = "iboard" 那个包的 version +function readCargoLockVersion() { + const text = fs.readFileSync(path.join(ROOT, 'src-tauri/Cargo.lock'), 'utf8') + const blocks = text.split(/^\[\[package\]\]$/m) + for (const b of blocks) { + if (/^name\s*=\s*"iboard"$/m.test(b)) { + const m = /^version\s*=\s*"([^"]+)"/m.exec(b) + if (m) return m[1] + } + } + return null +} + +// 组1 前端 (唯一手动管理点 = package.json) +const webVersion = String(readJson('package.json').version || '') +const webSources = [{ file: 'package.json', version: webVersion }] +const otaVersion = webVersion + '-ota' + +// 组2 外壳 (独立维护; 三处必须组内一致) +const shellSources = [ + { file: 'src-tauri/tauri.conf.json', version: String(readJson('src-tauri/tauri.conf.json').version || '') }, + { file: 'src-tauri/Cargo.toml', version: readCargoTomlVersion() }, + { file: 'src-tauri/Cargo.lock (iboard)', version: readCargoLockVersion() }, +] + +console.log('[check-version] 分支: ', branch || '(未知)') +for (const s of webSources) console.log(`[check-version] [前端] ${s.file.padEnd(28)} ${s.version || '(缺失)'}`) +for (const s of shellSources) console.log(`[check-version] [外壳] ${s.file.padEnd(28)} ${s.version || '(缺失)'}`) +console.log('[check-version] 产物版本: OTA =', otaVersion, '/ 外壳(APK versionName) =', shellSources[0].version) + +let failed = false + +// ---- A1. 前端组: 裸版本号 ---- +if (/-ota$/i.test(webVersion)) { + console.error('') + console.error(`[check-version] package.json 的 version 不能带 "-ota": ${webVersion}`) + console.error('[check-version] OTA 后缀由 scripts/build-ota.mjs 自动追加, 请只写裸版本号 (如 0.2.0)') + failed = true +} + +// ---- A2. 外壳组内一致 ---- +const shellBroken = shellSources.filter((s) => s.version !== shellSources[0].version || !s.version) +if (shellBroken.length) { + console.error('') + console.error('============================================================') + console.error('[check-version] 外壳版本号不一致 (tauri.conf.json ↔ Cargo.toml ↔ Cargo.lock), 构建中止') + console.error('============================================================') + for (const s of shellBroken) { + console.error(` ${s.file}: ${s.version || '(缺失)'} ≠ tauri.conf.json: ${shellSources[0].version}`) + } + failed = true +} + +// ---- B. 分支对应 (仅前端组; 外壳在 android-* 分支独立演进, 不参与) ---- +const m = /^dev-(\d+)(?:\.(\d+))?$/.exec(branch) +if (!m) { + console.log('[check-version] 非 dev-<版本> 分支, 跳过分支对应校验') +} else { + const want = m[2] === undefined ? `${m[1]}.` : `${m[1]}.${m[2]}` + if (!webVersion.startsWith(want)) { + console.error('') + console.error(`[check-version] 版本与分支不一致: 分支 "${branch}" 要求前端版本以 ${want} 开头, 实际是 ${webVersion}`) + console.error('[check-version] 请修改 package.json 的 version') + failed = true + } else { + console.log(`[check-version] 分支一致 (${branch} ↔ ${webVersion})`) + } +} + +if (failed) process.exit(1) +console.log('[check-version] 全部通过') diff --git a/scripts/etl-mariadb-to-pg.mjs b/scripts/etl-mariadb-to-pg.mjs new file mode 100644 index 0000000..ee5f178 --- /dev/null +++ b/scripts/etl-mariadb-to-pg.mjs @@ -0,0 +1,201 @@ +// 一次性数据迁移: 现网 MariaDB 只读快照 -> 0.2.1 PostgreSQL +// 运行 (在 app 容器内, 具备 mariadb 驱动与 PG 客户端): +// SRC_DATABASE_URL=mysql://... node scripts/etl-mariadb-to-pg.mjs +// 约束: 源库仅 SELECT; 目标库须为空 (Project 计数为 0), 除非 ETL_FORCE=1 +// 迁移内容见 docs/V0.2/离线编辑模块/0.2.1实施方案.md §6: +// - 任务域 6 表回填 uuid; Project 回填短编号 code; Task 按项目内 (createdAt,id) 顺序回填 number +// - 时序大表 (ServerNodeHistory/MihomeEnv*) 仅取最近 N 条 +// - 显式 id 插入后逐表 setval 校准序列 +import 'dotenv/config' +import crypto from 'node:crypto' +import mariadb from 'mariadb' +import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from '../generated/prisma/client.ts' + +// 顺序: 先父后子 +const TABLES = [ + ['User', 'user'], + ['Project', 'project'], + ['ProjectMember', 'projectMember'], + ['BoardColumn', 'boardColumn'], + ['Habit', 'habit'], + ['CheckList', 'checkList'], + ['CheckListTemplate', 'checkListTemplate'], + ['CheckListItem', 'checkListItem'], + ['CheckIn', 'checkIn'], + ['Task', 'task'], + ['TaskMember', 'taskMember'], + ['TaskTag', 'taskTag'], + ['Article', 'article'], + ['Strategy', 'strategy'], + ['Indicator', 'indicator'], + ['IndicatorPoint', 'indicatorPoint'], + ['News', 'news'], + ['Alert', 'alert'], + ['ServerNode', 'serverNode'], + ['ServerNodeHistory', 'serverNodeHistory'], + ['ServiceCheck', 'serviceCheck'], + ['Econ_SowInventory', 'econ_SowInventory'], + ['ScraperCache', 'scraperCache'], + ['OilPriceHistory', 'oilPriceHistory'], + ['MihomeAccount', 'mihomeAccount'], + ['MihomeHome', 'mihomeHome'], + ['MihomeDevice', 'mihomeDevice'], + ['MihomeEnvSample', 'mihomeEnvSample'], + ['MihomeEnvHour', 'mihomeEnvHour'], +] + +const ROW_CAP = { ServerNodeHistory: 20000, MihomeEnvSample: 20000, MihomeEnvHour: 20000 } + +const UUID_TABLES = new Set(['Project', 'ProjectMember', 'BoardColumn', 'Task', 'TaskMember', 'TaskTag']) + +function parseUrl(url) { + const u = new URL(url) + return { + host: u.hostname, + port: Number(u.port) || 3306, + user: decodeURIComponent(u.username), + password: decodeURIComponent(u.password), + database: u.pathname.replace(/^\//, ''), + } +} + +function parseJson(v) { + if (v === null || v === undefined || typeof v !== 'string') return v + try { return JSON.parse(v) } catch { return v } +} + +function toBigInt(v) { + if (v === null || v === undefined || typeof v === 'bigint') return v + return BigInt(v) +} + +// MariaDB TINYINT(1) 返回 0/1, Prisma Boolean 需要 true/false +function toBoolean(v) { + if (v === null || v === undefined) return v + return !!v +} + +function randCode(len) { + return crypto.randomBytes(len * 2).toString('hex').toUpperCase().replace(/[^A-Z0-9]/g, '').padEnd(len, 'X').slice(0, len) +} + +async function main() { + const src = process.env.SRC_DATABASE_URL + if (!src) throw new Error('SRC_DATABASE_URL 未配置') + const cfg = parseUrl(src) + + const conn = await mariadb.createConnection({ + host: cfg.host, port: cfg.port, user: cfg.user, password: cfg.password, database: cfg.database, connectTimeout: 8000, + }) + console.log('[etl] 源库已连接 (只读):', `${cfg.host}:${cfg.port}/${cfg.database}`) + + const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) + const prisma = new PrismaClient({ adapter }) + + const existing = await prisma.project.count() + if (existing > 0 && process.env.ETL_FORCE !== '1') { + console.log(`[etl] 目标库已有 ${existing} 个项目, 跳过 (ETL_FORCE=1 可强制)`) + await conn.end() + return + } + + // 目标 PG 列清单 (类型感知转换 + 源库多余列丢弃): 现网库可能比本分支 schema 新 + const targetColumns = new Map() + for (const [table] of TABLES) { + const cols = await prisma.$queryRawUnsafe( + `SELECT column_name, data_type FROM information_schema.columns WHERE table_schema = 'public' AND table_name = $1`, + table, + ) + targetColumns.set(table, new Map(cols.map((c) => [c.column_name, c.data_type]))) + } + + function adaptRow(table, r) { + const cols = targetColumns.get(table) + const out = {} + for (const [name, dataType] of cols) { + let v = r[name] + if (v === undefined) v = null + if (dataType === 'boolean') v = toBoolean(v) + else if (dataType === 'bigint') v = toBigInt(v) + else if (dataType === 'json' || dataType === 'jsonb') v = parseJson(v) + out[name] = v + } + return out + } + + // Project.code 回填: 优先按名称推导字母, 冲突加随机尾缀 + const projectRows = await conn.query('SELECT * FROM `Project`') + const usedCodes = new Set() + for (const p of projectRows) { + const letters = String(p.name || '').toUpperCase().replace(/[^A-Z0-9]/g, '') + let cand = '' + for (let i = 0; i < 30 && !cand; i++) { + const base = i === 0 + ? (letters.length >= 2 ? letters.slice(0, 8) : ('P' + String(p.id) + randCode(4)).slice(0, 8)) + : (letters.slice(0, 5) + randCode(3)).slice(0, 8) + if (base.length >= 2 && !usedCodes.has(base)) cand = base + } + if (!cand) cand = randCode(6) + usedCodes.add(cand) + p.code = cand + } + + // Task.number 回填: 项目内按 (createdAt, id) 顺序 1..n + const taskRows = (await conn.query('SELECT * FROM `Task`')).sort((a, b) => { + if (a.projectId !== b.projectId) return a.projectId - b.projectId + const ta = a.createdAt ? new Date(a.createdAt).getTime() : 0 + const tb = b.createdAt ? new Date(b.createdAt).getTime() : 0 + if (ta !== tb) return ta - tb + return a.id - b.id + }) + const taskNumber = new Map() + for (const r of taskRows) { + const key = String(r.projectId) + const next = (taskNumber.get(key) || 0) + 1 + taskNumber.set(key, next) + r.number = next + } + + const counts = {} + for (const [table, model] of TABLES) { + let rows + if (table === 'Project') rows = projectRows + else if (table === 'Task') rows = taskRows + else { + const cap = ROW_CAP[table] + rows = await conn.query(cap ? `SELECT * FROM \`${table}\` ORDER BY id DESC LIMIT ${cap}` : `SELECT * FROM \`${table}\``) + } + if (rows.length) { + const adapted = rows.map((r) => { + if (UUID_TABLES.has(table)) r.uuid = crypto.randomUUID() + return adaptRow(table, r) + }) + const chunk = 500 + for (let i = 0; i < adapted.length; i += chunk) { + await prisma[model].createMany({ data: adapted.slice(i, i + chunk) }) + } + } + counts[table] = rows.length + console.log(`[etl] ${table}: ${rows.length}`) + } + + // 序列校准: 显式插入 id 后必须 setval, 否则新插入主键冲突 + for (const [table] of TABLES) { + await prisma.$executeRawUnsafe( + `SELECT setval(pg_get_serial_sequence('"${table}"', 'id'), COALESCE((SELECT MAX(id) FROM "${table}"), 0) + 1, false)`, + ) + } + + await conn.end() + const summary = { + users: await prisma.user.count(), + projects: await prisma.project.count(), + tasks: await prisma.task.count(), + checkIns: await prisma.checkIn.count(), + news: await prisma.news.count(), + } + console.log('[etl] 完成:', JSON.stringify(summary)) +} + +main().catch((e) => { console.error('[etl] 失败:', e); process.exit(1) }) diff --git a/scripts/gen-tray-icons.mjs b/scripts/gen-tray-icons.mjs new file mode 100644 index 0000000..d492a0c --- /dev/null +++ b/scripts/gen-tray-icons.mjs @@ -0,0 +1,49 @@ +#!/usr/bin/env node +// 生成托盘深浅色单色图标 (W-THE-01): +// src-tauri/icons/tray-light.png 深色字形 —— 浅色任务栏用 +// src-tauri/icons/tray-dark.png 白色字形 —— 深色任务栏用 +// +// 做法: 不能只用 alpha 通道做剪影 —— 源图标是「深色圆角方块 + 白色 i」, +// 整个方块都是不透明的, 只取 alpha 会得到一块纯色方块, i 字母细节全丢。 +// 这里按**亮度**映射, 保留字母结构: +// 深色字形: alpha = (255-L)/255 → 方块保留深色, 白色 i 镂空透出任务栏 +// 浅色字形: alpha = max(L,40)/255 → i 白色高亮, 方块留一点淡淡衬底 +// 产物已提交进仓库, 仅图标变更时需要重跑: node scripts/gen-tray-icons.mjs +import sharp from 'sharp' +import { fileURLToPath } from 'node:url' +import path from 'node:path' + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const src = path.join(root, 'src-tauri', 'icons', '128x128.png') +const outDir = path.join(root, 'src-tauri', 'icons') + +const targets = [ + { file: 'tray-light.png', rgb: [31, 31, 31], mode: 'dark-glyph' }, // 深色字形 (浅色任务栏) + { file: 'tray-dark.png', rgb: [250, 250, 250], mode: 'light-glyph' }, // 白色字形 (深色任务栏) +] + +for (const t of targets) { + const { data, info } = await sharp(src) + .resize(32, 32, { fit: 'contain', background: { r: 0, g: 0, b: 0, alpha: 0 } }) + .ensureAlpha() + .raw() + .toBuffer({ resolveWithObject: true }) + const out = Buffer.alloc(info.width * info.height * 4) + for (let i = 0; i < info.width * info.height; i++) { + const r = data[i * 4] + const g = data[i * 4 + 1] + const b = data[i * 4 + 2] + const a = data[i * 4 + 3] + const lumi = 0.2126 * r + 0.7152 * g + 0.0722 * b + const alpha = + t.mode === 'dark-glyph' ? (a * (255 - lumi)) / 255 : (a * Math.max(lumi, 40)) / 255 + out[i * 4] = t.rgb[0] + out[i * 4 + 1] = t.rgb[1] + out[i * 4 + 2] = t.rgb[2] + out[i * 4 + 3] = Math.round(alpha) + } + await sharp(out, { raw: { width: info.width, height: info.height, channels: 4 } }) + .png() + .toFile(path.join(outDir, t.file)) + console.log('generated', t.file) +} diff --git a/scripts/verify-powersync-client.mjs b/scripts/verify-powersync-client.mjs new file mode 100644 index 0000000..1875a4e --- /dev/null +++ b/scripts/verify-powersync-client.mjs @@ -0,0 +1,218 @@ +// 0.2.1 闭环验证脚本 (Node 客户端): 同步下行 -> 本地离线写入 -> 上行 -> 服务端编号 -> 同步回填 +// 用法: node scripts/verify-powersync-client.mjs [BASE_URL] [EMAIL] [PASSWORD] +// BASE_URL 默认 http://bh.vps.honor3.com:13001 +// 依赖 (dev-only, 不进服务端镜像; 运行前补装): npm install --no-save @powersync/node better-sqlite3 +// 流程: +// 1. REST 登录 -> /api/powersync/token +// 2. Node SDK 连接 PowerSync, 等待首次同步, 校验 ETL 数据 (项目/任务/编号) +// 3. 本地新建一条验证任务 (number 为空, 模拟离线写入) +// 4. 等待 upload 完成并同步回填 -> 本地行应获得服务端分配的 number +// 5. 清理: 本地删除该任务 (上行级联删除), 恢复数据原状 +import 'dotenv/config' +import crypto from 'node:crypto' +import os from 'node:os' +import path from 'node:path' +import fs from 'node:fs' +import { column, Schema, Table, PowerSyncDatabase, createConsoleLogger, LogLevels } from '@powersync/node' + +const BASE = (process.argv[2] || 'http://bh.vps.honor3.com:13001').replace(/\/+$/, '') +const EMAIL = process.argv[3] || 'admin@iboard.local' +const PASSWORD = process.argv[4] || 'admin123' + +const uuid = () => crypto.randomUUID() +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) + +// 与 src/lib/powersync/schema.js 同构的最小 schema (验证只用到 Project/Task/TaskTag) +const AppSchema = new Schema({ + Project: new Table({ + uuid: column.text, code: column.text, name: column.text, ownerId: column.text, + isPersonal: column.integer, archived: column.integer, notes: column.text, + taskKinds: column.text, icon: column.text, createdAt: column.text, updatedAt: column.text, + }), + Task: new Table({ + uuid: column.text, projectId: column.text, columnId: column.text, parentId: column.text, + title: column.text, description: column.text, kind: column.text, markDate: column.text, + status: column.text, doneAt: column.text, assigneeId: column.text, creatorId: column.text, + extras: column.text, sortOrder: column.integer, number: column.integer, + createdAt: column.text, updatedAt: column.text, + }), + TaskTag: new Table({ uuid: column.text, taskId: column.text, name: column.text }), + TaskMember: new Table({ uuid: column.text, taskId: column.text, userId: column.text }), + ProjectMember: new Table({ uuid: column.text, projectId: column.text, userId: column.text, permission: column.text, createdAt: column.text }), + BoardColumn: new Table({ + uuid: column.text, projectId: column.text, name: column.text, sortOrder: column.integer, + boundStatus: column.text, retention: column.text, createdAt: column.text, updatedAt: column.text, + }), + User: new Table({ name: column.text, email: column.text }), +}) + +async function api(path, { method = 'GET', token, body } = {}) { + const res = await fetch(BASE + '/api' + path, { + method, + headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: 'Bearer ' + token } : {}) }, + body: body ? JSON.stringify(body) : undefined, + }) + const data = await res.json().catch(() => ({})) + if (!res.ok) throw new Error(`${path} -> ${res.status}: ${data.error || '请求失败'}`) + return data +} + +class Connector { + constructor(appToken, endpoint) { + // appToken: 应用 JWT (调业务 API 上行用); endpoint + sync JWT 由 fetchCredentials 提供 + this.appToken = appToken + this.endpoint = endpoint + } + async fetchCredentials() { + const cred = await api('/powersync/token', { method: 'POST', token: this.appToken }) + return { endpoint: cred.endpoint, token: cred.token } + } + async uploadData(database) { + const batch = await database.getCrudBatch() + if (!batch) return + const ops = [] + for (let i = 0; i < batch.crud.length; i++) { + const op = batch.crud[i] + let data = op.opData || {} + if (op.op !== 'DELETE') { + const row = await database.getOptional(`SELECT * FROM "${op.table}" WHERE id = ?`, [op.id]) + if (row) data = row + } + ops.push({ id: String(i), table: op.table, op: op.op === 'DELETE' ? 'DELETE' : 'PUT', uuid: op.id, data }) + } + const res = await fetch(BASE + '/api/powersync/upload', { + method: 'POST', + headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + this.appToken }, + body: JSON.stringify({ ops }), + }) + const out = await res.json().catch(() => ({})) + if (!res.ok) throw new Error('upload failed: ' + JSON.stringify(out)) + const failed = (out.results || []).filter((r) => !r.ok) + if (failed.length) console.log('[verify] upload 部分失败:', JSON.stringify(failed)) + await batch.complete() + } +} + +async function main() { + console.log(`[verify] BASE = ${BASE}`) + const login = await api('/auth/login', { method: 'POST', body: { email: EMAIL, password: PASSWORD } }) + console.log('[verify] 1. 登录 OK, user:', login.user.email) + const cred = await api('/powersync/token', { method: 'POST', token: login.token }) + if (!cred.endpoint) throw new Error('POWERSYNC_URL 未配置 (服务端 /powersync/token 返回空 endpoint)') + console.log('[verify] 2. 同步凭证 OK, endpoint:', cred.endpoint) + + const dbFile = path.join(os.tmpdir(), `iboard-verify-${Date.now()}.db`) + const logger = createConsoleLogger({ minLevel: LogLevels.debug }) + const db = new PowerSyncDatabase({ + schema: AppSchema, + database: { dbFilename: dbFile, databaseWorkerLogLevel: LogLevels.debug }, + sync: { logLevel: LogLevels.debug }, + logger, + }) + db.registerListener({ + statusChanged: (s) => { + const c = s?.dataFlowStatus || {} + console.log(`[verify] sync status: connected=${s?.connected} connecting=${s?.connecting} err=${c.downloadError?.message || c.uploadError?.message || '-'}`) + }, + }) + try { + await db.connect(new Connector(login.token, cred.endpoint)) + console.log('[verify] connect() 已建立') + } catch (e) { + console.error('[verify] connect 失败:', e?.message) + throw e + } + await Promise.race([ + db.waitForFirstSync(), + sleep(45000).then(() => { throw new Error('waitForFirstSync 超时 (45s)') }), + ]).catch(async (e) => { + console.log('[verify] waitForFirstSync 未完成, 轮询内部状态...') + for (let i = 0; i < 10; i++) { + await sleep(3000) + const st = db.currentStatus + const counts = { + projects: (await db.getOptional('SELECT COUNT(*) AS n FROM Project'))?.n, + tasks: (await db.getOptional('SELECT COUNT(*) AS n FROM Task'))?.n, + } + console.log(`[verify] poll#${i}:`, JSON.stringify({ + connected: st?.connected, connecting: st?.connecting, hasSynced: st?.hasSynced, + lastSyncedAt: st?.lastSyncedAt, dl: st?.dataFlowStatus?.downloadError?.message, + ul: st?.dataFlowStatus?.uploadError?.message, ...counts, + })) + if (st?.hasSynced && counts.projects > 0) { + console.log('[verify] 轮询确认同步已完成') + return + } + } + throw e + }) + console.log('[verify] 3. 首次同步完成') + + const projects = await db.getAll('SELECT id, uuid, code, name FROM Project ORDER BY id') + console.log(`[verify] 项目 ${projects.length} 个:`, projects.map((p) => `${p.code || '?'}(${p.name})`).join(', ')) + const tasks = await db.getAll('SELECT COUNT(*) AS n FROM Task') + const numbered = await db.getAll('SELECT COUNT(*) AS n FROM Task WHERE number IS NOT NULL') + console.log(`[verify] 任务 ${tasks[0].n} 条 (已有编号 ${numbered[0].n} 条)`) + if (!projects.length || tasks[0].n === 0) throw new Error('同步数据为空, ETL 未生效?') + + // 本地新建验证任务 (模拟离线写入: number 为空, 等服务端分配) + const target = projects.find((p) => p.code) || projects[0] + const firstCol = await db.get(`SELECT id, boundStatus FROM BoardColumn WHERE projectId = ? ORDER BY sortOrder, id LIMIT 1`, [target.id]) + const marker = `离线闭环验证-${Date.now()}` + const newId = uuid() + const ts = new Date().toISOString() + await db.writeTransaction(async (tx) => { + await tx.execute( + `INSERT INTO Task (id, uuid, projectId, columnId, parentId, title, description, kind, markDate, status, doneAt, assigneeId, creatorId, extras, sortOrder, number, createdAt, updatedAt) + VALUES (?, ?, ?, ?, NULL, ?, '', NULL, NULL, ?, NULL, NULL, ?, '[]', 0, NULL, ?, ?)`, + [newId, newId, target.id, firstCol.id, marker, firstCol.boundStatus || 'todo', String(login.user.id), ts, ts], + ) + }) + console.log(`[verify] 4. 本地写入任务 "${marker}" (number 为空), 等待上行 + 服务端定号 + 同步回填...`) + + let syncedBack = null + for (let i = 0; i < 30; i++) { + await sleep(2000) + const row = await db.getOptional('SELECT number, updatedAt FROM Task WHERE id = ?', [newId]) + const localCheck = await db.getOptional(`SELECT 1 AS x FROM ps_crud LIMIT 1`) + if (!localCheck) { + // 队列已清空 (上行完成); 等待下行回填 number + if (row && row.number != null) { syncedBack = row; break } + if (i > 10 && row && row.number == null) continue + } + } + if (!syncedBack) throw new Error('超时: 未观测到服务端编号回填') + console.log(`[verify] 5. 闭环 OK! 服务端分配编号: ${target.code}-${syncedBack.number} (项目 ${target.code})`) + + // 直接从服务端核对 (REST 视角: REST 仍按 int id 寻址, 通过列表接口按 uuid 匹配) + const plist = await api('/projects', { token: login.token }) + const pj = plist.items.find((p) => p.uuid === target.uuid) + if (!pj) throw new Error('服务端确认失败: 项目列表中找不到目标项目') + const tlist = await api(`/projects/${pj.id}/tasks`, { token: login.token }) + const hit = tlist.items.find((t) => t.uuid === newId) + if (!hit || hit.number == null) throw new Error('服务端确认失败: 任务不存在或未定号') + console.log(`[verify] 6. 服务端确认: number=${hit.number}, uuid=${hit.uuid.slice(0, 13)}...`) + + // 清理验证数据 (本地删除 -> 上行删除) + await db.execute('DELETE FROM Task WHERE id = ?', [newId]) + for (let i = 0; i < 15; i++) { + await sleep(2000) + const left = await db.getOptional('SELECT 1 AS x FROM ps_crud LIMIT 1') + if (!left) break + } + const gone = await api(`/projects/${pj.id}/tasks`, { token: login.token }) + if (!gone.items.some((t) => t.uuid === newId)) console.log('[verify] 7. 验证任务已从服务端清除') + else throw new Error('验证任务删除未同步到服务端') + + db.close() + try { + fs.rmSync(dbFile, { force: true }) + fs.rmSync(dbFile + '-journal', { force: true }) + } catch { /* Windows 下可能被占用, 留在临时目录无害 */ } + console.log('[verify] 全部通过: 下行同步 / 离线写入 / 上行幂等 / 服务端编号 / 回填 / 删除同步') +} + +main().catch((e) => { + console.error('[verify] 失败:', e.message) + process.exit(1) +}) diff --git a/scripts/wsl/build-apk.sh b/scripts/wsl/build-apk.sh index b723dae..12cc790 100644 --- a/scripts/wsl/build-apk.sh +++ b/scripts/wsl/build-apk.sh @@ -1,143 +1,143 @@ -#!/usr/bin/env bash -# iBoard Android APK 打包 -# 用法: bash scripts/wsl/build-apk.sh [debug|release] -# release 模式自动构建 4 个 ABI 并出 universal APK -set -euo pipefail - -WIN_PROJECT='/mnt/d/workbench/iboard' -cd "$WIN_PROJECT" - -VARIANT="${1:-debug}" -echo "构建变体: $VARIANT" - -# 环境变量 -export ANDROID_HOME="${ANDROID_HOME:-$HOME/android-sdk}" -export ANDROID_NDK_HOME="${ANDROID_NDK_HOME:-$ANDROID_HOME/ndk/27.2.12479018}" -# 关键: 把 NDK toolchain bin 加进 PATH, .cargo/config.toml 里 -# linker = "aarch64-linux-android24-clang" 这种短名才能被解析到 -NDK_TOOLCHAIN_BIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin" -if [ ! -d "$NDK_TOOLCHAIN_BIN" ]; then - echo "ERROR: NDK toolchain bin 不存在: $NDK_TOOLCHAIN_BIN" - echo "请确认 ANDROID_NDK_HOME 正确指向 NDK 27.2.x 目录" - exit 1 -fi -export PATH="$NDK_TOOLCHAIN_BIN:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$HOME/.cargo/bin:$PATH" - -# 0. 首次需要 android 工程 -cd src-tauri -if [ ! -d "gen/android" ]; then - echo "=== 0. 初始化 Android 工程 (一次性) ===" - npx tauri android init -fi -cd "$WIN_PROJECT" - -# Android 后端地址, 写到前端 bundle 里 (覆盖默认的 /api 相对路径) -export VITE_API_BASE="${VITE_API_BASE:-http://bh.vps.honor3.com:3001}" -echo "VITE_API_BASE=$VITE_API_BASE" - -# 1. 先构建前端, 让 Tauri android 工程能拿到 dist/ -echo "=== 1. 构建前端 ===" -npm run build - -# 1.5 拷贝 dist/ 到 Android 工程 assets/, 绕过 Tauri CLI 时这步要做 -# Tauri CLI 默认在 android build 前会干这事, 我们走直 gradle 要自己补上 -ANDROID_ASSETS="$WIN_PROJECT/src-tauri/gen/android/app/src/main/assets" -mkdir -p "$ANDROID_ASSETS" -# 清空旧的 (避免上次的残余) -find "$ANDROID_ASSETS" -mindepth 1 -maxdepth 1 ! -name dexopt ! -name tauri.conf.json -exec rm -rf {} + -cp -r dist/. "$ANDROID_ASSETS/" -echo "前端 bundle 已拷贝到 $ANDROID_ASSETS" -ls -la "$ANDROID_ASSETS" | head -10 - -# 2. 走不同路径 -cd src-tauri - -# 全部 4 个 Android 架构的 rustBuild 任务 (Tauri 2 默认会编 4 个) -RUST_BUILD_TASKS=( - ":app:rustBuildArm64Release" - ":app:rustBuildArmRelease" - ":app:rustBuildX86_64Release" - ":app:rustBuildX86Release" -) -SKIP_ARGS=() -for t in "${RUST_BUILD_TASKS[@]}"; do SKIP_ARGS+=(-x "${t#:}"); done - -# 4 个 Android 目标的 cargo target triple -ANDROID_TARGETS=( - "aarch64-linux-android" - "armv7-linux-androideabi" - "x86_64-linux-android" - "i686-linux-android" -) - -if [ "$VARIANT" = "release" ]; then - echo "[release] 手工 cargo 编 lib (4 个 ABI), gradle 只跑 packaging (跳过 rustBuild* 避免 --release 重复 bug)" - - for target in "${ANDROID_TARGETS[@]}"; do - echo " [cargo] build lib release for $target ..." - cargo build \ - --target "$target" \ - --features custom-protocol \ - --lib \ - --release - done - - echo " [gradle] assembleRelease -x rustBuild* ..." - cd gen/android - ./gradlew --no-daemon assembleRelease "${SKIP_ARGS[@]}" - - APK_DIR="$WIN_PROJECT/src-tauri/gen/android/app/build/outputs/apk/universal/release" - # Gradle 配了 signingConfig 就输出 app-universal-release.apk, - # 否则输出 app-universal-release-unsigned.apk, 按文件名判断是否已签名 - SIGNED_APK="$APK_DIR/app-universal-release.apk" - UNSIGNED_APK="$APK_DIR/app-universal-release-unsigned.apk" - - if [ -f "$SIGNED_APK" ]; then - APK_PATH="$SIGNED_APK" - APK_STATUS="已签名" - elif [ -f "$UNSIGNED_APK" ]; then - APK_PATH="$UNSIGNED_APK" - APK_STATUS="未签名" - else - APK_PATH="" - APK_STATUS="未找到" - fi - - echo - echo "=== 产物 ($APK_STATUS) ===" - if [ -n "$APK_PATH" ]; then - ls -lh "$APK_PATH" - else - ls -lh "$APK_DIR"/*.apk 2>/dev/null || echo "(未找到 APK)" - fi - - if [ "$APK_STATUS" = "已签名" ]; then - echo - echo "=== 安装到设备 ===" - echo " adb install -r $APK_PATH" - elif [ "$APK_STATUS" = "未签名" ]; then - echo - echo "=== Release 签名 (按需) ===" - echo "1. 生成 keystore (一次性):" - echo " keytool -genkey -v -keystore ~/iboard-release.keystore -alias iboard -keyalg RSA -keysize 2048 -validity 10000" - echo "2. 在 ~/.gradle/gradle.properties 写:" - echo " IBOARD_UPLOAD_STORE_FILE=/home/$(whoami)/iboard-release.keystore" - echo " IBOARD_UPLOAD_KEY_ALIAS=iboard" - echo " IBOARD_UPLOAD_STORE_PASSWORD=..." - echo " IBOARD_UPLOAD_KEY_PASSWORD=..." - echo "3. 重跑本脚本 (Gradle 读 gradle.properties 里的 IBOARD_UPLOAD_* 自动用 releaseSigning)" - echo - echo "=== 临时用 debug keystore 手动签 (仅测试) ===" - echo " \$ANDROID_HOME/build-tools/*/apksigner sign --ks ~/.android/debug.keystore \" - echo " --ks-pass pass:android --key-pass pass:android \" - echo " $APK_PATH" - fi -else - echo "[debug] Tauri CLI debug 路径 (不传 --release 不会触发 bug), 直接走" - npx tauri android build --apk - - APK_DIR="$WIN_PROJECT/src-tauri/target/debug/bundle/android/app/build/outputs/apk/debug" - echo - echo "=== 产物 ===" - ls -lh "$APK_DIR"/*.apk 2>/dev/null || echo "(未找到 APK)" -fi +#!/usr/bin/env bash +# iBoard Android APK 打包 +# 用法: bash scripts/wsl/build-apk.sh [debug|release] +# release 模式自动构建 4 个 ABI 并出 universal APK +set -euo pipefail + +WIN_PROJECT='/mnt/d/workbench/iboard' +cd "$WIN_PROJECT" + +VARIANT="${1:-debug}" +echo "构建变体: $VARIANT" + +# 环境变量 +export ANDROID_HOME="${ANDROID_HOME:-$HOME/android-sdk}" +export ANDROID_NDK_HOME="${ANDROID_NDK_HOME:-$ANDROID_HOME/ndk/27.2.12479018}" +# 关键: 把 NDK toolchain bin 加进 PATH, .cargo/config.toml 里 +# linker = "aarch64-linux-android24-clang" 这种短名才能被解析到 +NDK_TOOLCHAIN_BIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin" +if [ ! -d "$NDK_TOOLCHAIN_BIN" ]; then + echo "ERROR: NDK toolchain bin 不存在: $NDK_TOOLCHAIN_BIN" + echo "请确认 ANDROID_NDK_HOME 正确指向 NDK 27.2.x 目录" + exit 1 +fi +export PATH="$NDK_TOOLCHAIN_BIN:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$HOME/.cargo/bin:$PATH" + +# 0. 首次需要 android 工程 +cd src-tauri +if [ ! -d "gen/android" ]; then + echo "=== 0. 初始化 Android 工程 (一次性) ===" + npx tauri android init +fi +cd "$WIN_PROJECT" + +# Android 后端地址, 写到前端 bundle 里 (覆盖默认的 /api 相对路径) +export VITE_API_BASE="${VITE_API_BASE:-https://iboard.honor3.com}" +echo "VITE_API_BASE=$VITE_API_BASE" + +# 1. 先构建前端, 让 Tauri android 工程能拿到 dist/ +echo "=== 1. 构建前端 ===" +npm run build + +# 1.5 拷贝 dist/ 到 Android 工程 assets/, 绕过 Tauri CLI 时这步要做 +# Tauri CLI 默认在 android build 前会干这事, 我们走直 gradle 要自己补上 +ANDROID_ASSETS="$WIN_PROJECT/src-tauri/gen/android/app/src/main/assets" +mkdir -p "$ANDROID_ASSETS" +# 清空旧的 (避免上次的残余) +find "$ANDROID_ASSETS" -mindepth 1 -maxdepth 1 ! -name dexopt ! -name tauri.conf.json -exec rm -rf {} + +cp -r dist/. "$ANDROID_ASSETS/" +echo "前端 bundle 已拷贝到 $ANDROID_ASSETS" +ls -la "$ANDROID_ASSETS" | head -10 + +# 2. 走不同路径 +cd src-tauri + +# 全部 4 个 Android 架构的 rustBuild 任务 (Tauri 2 默认会编 4 个) +RUST_BUILD_TASKS=( + ":app:rustBuildArm64Release" + ":app:rustBuildArmRelease" + ":app:rustBuildX86_64Release" + ":app:rustBuildX86Release" +) +SKIP_ARGS=() +for t in "${RUST_BUILD_TASKS[@]}"; do SKIP_ARGS+=(-x "${t#:}"); done + +# 4 个 Android 目标的 cargo target triple +ANDROID_TARGETS=( + "aarch64-linux-android" + "armv7-linux-androideabi" + "x86_64-linux-android" + "i686-linux-android" +) + +if [ "$VARIANT" = "release" ]; then + echo "[release] 手工 cargo 编 lib (4 个 ABI), gradle 只跑 packaging (跳过 rustBuild* 避免 --release 重复 bug)" + + for target in "${ANDROID_TARGETS[@]}"; do + echo " [cargo] build lib release for $target ..." + cargo build \ + --target "$target" \ + --features custom-protocol \ + --lib \ + --release + done + + echo " [gradle] assembleRelease -x rustBuild* ..." + cd gen/android + ./gradlew --no-daemon assembleRelease "${SKIP_ARGS[@]}" + + APK_DIR="$WIN_PROJECT/src-tauri/gen/android/app/build/outputs/apk/universal/release" + # Gradle 配了 signingConfig 就输出 app-universal-release.apk, + # 否则输出 app-universal-release-unsigned.apk, 按文件名判断是否已签名 + SIGNED_APK="$APK_DIR/app-universal-release.apk" + UNSIGNED_APK="$APK_DIR/app-universal-release-unsigned.apk" + + if [ -f "$SIGNED_APK" ]; then + APK_PATH="$SIGNED_APK" + APK_STATUS="已签名" + elif [ -f "$UNSIGNED_APK" ]; then + APK_PATH="$UNSIGNED_APK" + APK_STATUS="未签名" + else + APK_PATH="" + APK_STATUS="未找到" + fi + + echo + echo "=== 产物 ($APK_STATUS) ===" + if [ -n "$APK_PATH" ]; then + ls -lh "$APK_PATH" + else + ls -lh "$APK_DIR"/*.apk 2>/dev/null || echo "(未找到 APK)" + fi + + if [ "$APK_STATUS" = "已签名" ]; then + echo + echo "=== 安装到设备 ===" + echo " adb install -r $APK_PATH" + elif [ "$APK_STATUS" = "未签名" ]; then + echo + echo "=== Release 签名 (按需) ===" + echo "1. 生成 keystore (一次性):" + echo " keytool -genkey -v -keystore ~/iboard-release.keystore -alias iboard -keyalg RSA -keysize 2048 -validity 10000" + echo "2. 在 ~/.gradle/gradle.properties 写:" + echo " IBOARD_UPLOAD_STORE_FILE=/home/$(whoami)/iboard-release.keystore" + echo " IBOARD_UPLOAD_KEY_ALIAS=iboard" + echo " IBOARD_UPLOAD_STORE_PASSWORD=..." + echo " IBOARD_UPLOAD_KEY_PASSWORD=..." + echo "3. 重跑本脚本 (Gradle 读 gradle.properties 里的 IBOARD_UPLOAD_* 自动用 releaseSigning)" + echo + echo "=== 临时用 debug keystore 手动签 (仅测试) ===" + echo " \$ANDROID_HOME/build-tools/*/apksigner sign --ks ~/.android/debug.keystore \" + echo " --ks-pass pass:android --key-pass pass:android \" + echo " $APK_PATH" + fi +else + echo "[debug] Tauri CLI debug 路径 (不传 --release 不会触发 bug), 直接走" + npx tauri android build --apk + + APK_DIR="$WIN_PROJECT/src-tauri/target/debug/bundle/android/app/build/outputs/apk/debug" + echo + echo "=== 产物 ===" + ls -lh "$APK_DIR"/*.apk 2>/dev/null || echo "(未找到 APK)" +fi diff --git a/server/checkin/period.js b/server/checkin/period.js new file mode 100644 index 0000000..4f958c0 --- /dev/null +++ b/server/checkin/period.js @@ -0,0 +1,120 @@ +// 打卡周期计算: 全部以 Asia/Shanghai 的"本地日历日"为准 +// 日期统一用 "YYYY-MM-DD" 字符串 (字典序 = 时间序, 可直接用于范围查询) + +export const TZ = 'Asia/Shanghai' + +const FMT = new Intl.DateTimeFormat('en-CA', { + timeZone: TZ, + year: 'numeric', + month: '2-digit', + day: '2-digit', +}) + +/** 任意时间 -> 本地日历日 "YYYY-MM-DD" */ +export function toDateStr(d = new Date()) { + return FMT.format(d instanceof Date ? d : new Date(d)) +} + +/** "YYYY-MM-DD" -> UTC 零点的 Date (仅用于日期运算, 不做时区展示) */ +function toUTC(s) { + const [y, m, d] = String(s).split('-').map(Number) + return new Date(Date.UTC(y, (m || 1) - 1, d || 1)) +} + +function fromUTC(dt) { + return dt.toISOString().slice(0, 10) +} + +export function addDays(s, n) { + const d = toUTC(s) + d.setUTCDate(d.getUTCDate() + n) + return fromUTC(d) +} + +export function addMonths(s, n) { + const [y, m] = String(s).split('-').map(Number) + const first = new Date(Date.UTC(y, (m || 1) - 1 + n, 1)) + const day = Number(String(s).slice(8, 10)) || 1 + const lastDay = new Date(Date.UTC(first.getUTCFullYear(), first.getUTCMonth() + 1, 0)).getUTCDate() + first.setUTCDate(Math.min(day, lastDay)) + return fromUTC(first) +} + +/** 周一为一周第一天 */ +export function startOfWeek(s) { + const dow = (toUTC(s).getUTCDay() + 6) % 7 // 0=周一 + return addDays(s, -dow) +} + +export function endOfWeek(s) { + return addDays(startOfWeek(s), 6) +} + +export function startOfMonth(s) { + return String(s).slice(0, 8) + '01' +} + +export function endOfMonth(s) { + const [y, m] = String(s).split('-').map(Number) + return fromUTC(new Date(Date.UTC(y, m || 1, 0))) +} + +export function startOfYear(s) { + return String(s).slice(0, 4) + '-01-01' +} + +export function endOfYear(s) { + return String(s).slice(0, 4) + '-12-31' +} + +export const PERIODS = ['day', 'week', 'month', 'year'] + +export const PERIOD_LABEL = { day: '每天', week: '每周', month: '每月', year: '每年' } + +/** 周期窗口 [start, end] (闭区间, 本地日历日) */ +export function periodRange(period, s) { + switch (period) { + case 'week': return { start: startOfWeek(s), end: endOfWeek(s) } + case 'month': return { start: startOfMonth(s), end: endOfMonth(s) } + case 'year': return { start: startOfYear(s), end: endOfYear(s) } + default: return { start: s, end: s } + } +} + +/** 周期窗口内的天数 */ +export function periodDays(period, s) { + const { start, end } = periodRange(period, s) + return Math.round((toUTC(end) - toUTC(start)) / 86400000) + 1 +} + +/** + * 日均期望次数: 用于日历上"某一天是否完成了当天应做的份额" + * - 每天 n 次 -> n + * - 每周 n 次 -> ceil(n / 7) + * - 每月 n 次 -> ceil(n / 当月天数) + * - 每年 n 次 -> ceil(n / 365) + * 环形进度仍以"周期完成度"(周期内累计次数 / n) 为准, 两者不冲突 + */ +export function expectedPerDay(period, target, s) { + const n = Math.max(1, Number(target) || 1) + if (period === 'week') return Math.max(1, Math.ceil(n / 7)) + if (period === 'month') return Math.max(1, Math.ceil(n / periodDays('month', s))) + if (period === 'year') return Math.max(1, Math.ceil(n / 365)) + return n +} + +/** 该周期在日期 d 之前是否已完整结束 (用于 streak 判断: 未结束的周期不算断签) */ +export function periodIsClosed(period, d, today) { + const { end } = periodRange(period, d) + return end < today +} + +/** 遍历 [start, end] 闭区间的每一天 */ +export function eachDay(start, end, fn) { + let cur = start + let guard = 0 + while (cur <= end && guard++ < 4000) { + fn(cur) + cur = addDays(cur, 1) + } +} diff --git a/server/cron.js b/server/cron.js index c8f9a62..a5fa86c 100644 --- a/server/cron.js +++ b/server/cron.js @@ -1,94 +1,110 @@ -import cron from 'node-cron' -import { prisma } from './db.js' -import mysql from 'mysql2/promise' -import { checkAfterRefresh } from './alert-engine.js' -import { ApizeroError, oil, history } from './apizero/index.js' - -// scripts/ 下的可选脚本模块 (部署容器可能缺失或依赖不全): 惰性加载, -// 单个 fetcher 缺失只降级对应任务, 不拖垮 cron 整体注册。 -async function importNewsFetcher() { - const mod = await import('../scripts/fetchers/news.fetcher.js') - return { fetchNewsList: mod.fetchNewsList, persistNews: mod.persistNews } -} - -async function alertError(taskName, message) { - try { - const { insertCronAlert } = await import('../scripts/lib/cron-alert.js') - await insertCronAlert(taskName, message) - } catch (e) { - console.error('[cron] 报警写入失败(忽略):', e.message) - } -} - -function parseUrl(url) { - const u = new URL(url) - return { host: u.hostname, port: Number(u.port) || 3306, user: decodeURIComponent(u.username), password: decodeURIComponent(u.password), database: u.pathname.replace(/^\//, '') } -} - -async function openConn() { - return mysql.createConnection(parseUrl(process.env.DATABASE_URL)) -} - -// 每天 6:00 (Asia/Shanghai) 拉取新闻 + 跑跨源对比, 出错生成报警 -function scheduleNews() { - cron.schedule('0 6 * * *', async () => { - console.log('[cron] 6:00 触发') - try { - const { fetchNewsList, persistNews } = await importNewsFetcher() - const list = await fetchNewsList() - const r = await persistNews(list) - console.log('[cron] 新闻入库: 新增 ' + r.inserted + ' / 跳过 ' + r.skipped) - const conn = await openConn() - try { - const a = await checkAfterRefresh(conn) - console.log('[cron] 报警检测: 新增 ' + a.inserted + ' 条 (扫描 ' + a.scanned + ')') - } finally { await conn.end() } - } catch (e) { - console.error('[cron] 任务失败:', e.message) - await alertError('新闻拉取+报警检测', e.message) - } - }, { timezone: 'Asia/Shanghai' }) - console.log('[cron] 已注册: 每天 06:00 拉取新闻 + 跨源报警检测') -} - -// 每天 6:10 (Asia/Shanghai) 抓浙江油价历史, 出错生成报警 -// 逻辑: 拉今年 schedule, 检查待定且 <= 昨天的调价日, 命中就抓 price?province=浙江 存库 -// - 0 命中: 不调 apizero (省配额) -// - >0 命中: 调一次, 4 个油品批量入库 (unique 兜底去重) -function scheduleOilHistory() { - cron.schedule('10 6 * * *', async () => { - console.log('[cron] 6:10 油价历史触发') - try { - const year = new Date().getFullYear() - const sched = await oil.getSchedule(year) - const list = sched.schedule || [] - // 待定 且 日期 <= 昨天 (用 Asia/Shanghai 时区推导, 与 cron 触发时区对齐) - const todayStr = new Date().toLocaleDateString('en-CA', { timeZone: 'Asia/Shanghai' }) - const today = new Date(todayStr + 'T00:00:00+08:00') - const yesterdayMs = today.getTime() - 86400000 - const pendingPast = list.filter((it) => { - if (it.status !== '待定') return false - const t = new Date(it.date + 'T00:00:00').getTime() - return Number.isFinite(t) && t <= yesterdayMs - }) - console.log('[cron] 待定且已过的调价日: ' + pendingPast.length + ' 个') - if (pendingPast.length === 0) { - console.log('[cron] 油价历史: 0 命中, 跳过 apizero 调用') - return - } - const r = await history.fetchAndStore() - console.log('[cron] 油价历史入库: 新增 ' + r.inserted + ' / 跳过 ' + r.skipped + ' (date=' + r.date + ')') - } catch (e) { - if (e instanceof ApizeroError) { - console.error('[cron] 油价历史 apizero 错误: code=' + e.code + ' msg=' + e.message) - } else { - console.error('[cron] 油价历史任务失败:', e.message) - } - await alertError('浙江油价历史抓取', e.message) - } - }, { timezone: 'Asia/Shanghai' }) - console.log('[cron] 已注册: 每天 06:10 浙江油价历史抓取') -} - -scheduleNews() -scheduleOilHistory() +import cron from 'node-cron' +import { prisma } from './db.js' +import mysql from 'mysql2/promise' +import { checkAfterRefresh } from './alert-engine.js' +import { ApizeroError, oil, history } from './apizero/index.js' + +// scripts/ 下的可选脚本模块 (部署容器可能缺失或依赖不全): 惰性加载, +// 单个 fetcher 缺失只降级对应任务, 不拖垮 cron 整体注册。 +async function importNewsFetcher() { + const mod = await import('../scripts/fetchers/news.fetcher.js') + return { fetchNewsList: mod.fetchNewsList, persistNews: mod.persistNews } +} + +async function alertError(taskName, message) { + try { + const { insertCronAlert } = await import('../scripts/lib/cron-alert.js') + await insertCronAlert(taskName, message) + } catch (e) { + console.error('[cron] 报警写入失败(忽略):', e.message) + } +} + +function parseUrl(url) { + const u = new URL(url) + return { host: u.hostname, port: Number(u.port) || 3306, user: decodeURIComponent(u.username), password: decodeURIComponent(u.password), database: u.pathname.replace(/^\//, '') } +} + +async function openConn() { + return mysql.createConnection(parseUrl(process.env.DATABASE_URL)) +} + +// 每天 6:00 (Asia/Shanghai) 拉取新闻 + 跑跨源对比, 出错生成报警 +function scheduleNews() { + cron.schedule('0 6 * * *', async () => { + console.log('[cron] 6:00 触发') + try { + const { fetchNewsList, persistNews } = await importNewsFetcher() + const list = await fetchNewsList() + const r = await persistNews(list) + console.log('[cron] 新闻入库: 新增 ' + r.inserted + ' / 跳过 ' + r.skipped) + const conn = await openConn() + try { + const a = await checkAfterRefresh(conn) + console.log('[cron] 报警检测: 新增 ' + a.inserted + ' 条 (扫描 ' + a.scanned + ')') + } finally { await conn.end() } + } catch (e) { + console.error('[cron] 任务失败:', e.message) + await alertError('新闻拉取+报警检测', e.message) + } + }, { timezone: 'Asia/Shanghai' }) + console.log('[cron] 已注册: 每天 06:00 拉取新闻 + 跨源报警检测') +} + +// 每天 6:10 (Asia/Shanghai) 抓浙江油价历史, 出错生成报警 +// 逻辑: 拉今年 schedule, 检查待定且 <= 昨天的调价日, 命中就抓 price?province=浙江 存库 +// - 0 命中: 不调 apizero (省配额) +// - >0 命中: 调一次, 4 个油品批量入库 (unique 兜底去重) +function scheduleOilHistory() { + cron.schedule('10 6 * * *', async () => { + console.log('[cron] 6:10 油价历史触发') + try { + const year = new Date().getFullYear() + const sched = await oil.getSchedule(year) + const list = sched.schedule || [] + // 待定 且 日期 <= 昨天 (用 Asia/Shanghai 时区推导, 与 cron 触发时区对齐) + const todayStr = new Date().toLocaleDateString('en-CA', { timeZone: 'Asia/Shanghai' }) + const today = new Date(todayStr + 'T00:00:00+08:00') + const yesterdayMs = today.getTime() - 86400000 + const pendingPast = list.filter((it) => { + if (it.status !== '待定') return false + const t = new Date(it.date + 'T00:00:00').getTime() + return Number.isFinite(t) && t <= yesterdayMs + }) + console.log('[cron] 待定且已过的调价日: ' + pendingPast.length + ' 个') + if (pendingPast.length === 0) { + console.log('[cron] 油价历史: 0 命中, 跳过 apizero 调用') + return + } + const r = await history.fetchAndStore() + console.log('[cron] 油价历史入库: 新增 ' + r.inserted + ' / 跳过 ' + r.skipped + ' (date=' + r.date + ')') + } catch (e) { + if (e instanceof ApizeroError) { + console.error('[cron] 油价历史 apizero 错误: code=' + e.code + ' msg=' + e.message) + } else { + console.error('[cron] 油价历史任务失败:', e.message) + } + await alertError('浙江油价历史抓取', e.message) + } + }, { timezone: 'Asia/Shanghai' }) + console.log('[cron] 已注册: 每天 06:10 浙江油价历史抓取') +} + +// 每小时兜底: 任务自动归档扫描 (T-ARC-02; 项目加载时另有读时惰性执行) +function scheduleAutoArchive() { + cron.schedule('30 * * * *', async () => { + try { + const { runAutoArchiveSweep } = await import('./task-domain/archive.js') + const n = await runAutoArchiveSweep() + if (n > 0) console.log('[cron] 自动归档:', n, '条') + } catch (e) { + console.error('[cron] 自动归档失败:', e.message) + await alertError('任务自动归档', e.message) + } + }, { timezone: 'Asia/Shanghai' }) + console.log('[cron] 已注册: 每小时 30 分 任务自动归档扫描') +} + +scheduleNews() +scheduleOilHistory() +scheduleAutoArchive() diff --git a/server/db.js b/server/db.js index fbc4299..0541846 100644 --- a/server/db.js +++ b/server/db.js @@ -1,18 +1,7 @@ -// 服务端 Prisma 单例, 使用 mariadb 适配器 +// 服务端 Prisma 单例, 使用 pg 适配器 (0.2.1: 数据库迁移到 PostgreSQL) import 'dotenv/config' -import { PrismaMariaDb } from '@prisma/adapter-mariadb' +import { PrismaPg } from '@prisma/adapter-pg' import { PrismaClient } from '../generated/prisma/client.ts' -function parseDatabaseUrl(url) { - const u = new URL(url) - return { - host: u.hostname, - port: Number(u.port) || 3306, - user: decodeURIComponent(u.username), - password: decodeURIComponent(u.password), - database: u.pathname.replace(/^\//, ''), - } -} - -const adapter = new PrismaMariaDb(parseDatabaseUrl(process.env.DATABASE_URL)) -export const prisma = new PrismaClient({ adapter }) \ No newline at end of file +const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }) +export const prisma = new PrismaClient({ adapter }) diff --git a/server/index.js b/server/index.js index 60372a3..1d803de 100644 --- a/server/index.js +++ b/server/index.js @@ -12,6 +12,11 @@ import otaRouter from './routes/ota.js' import apizeroRouter from './routes/apizero.js' import monitorsRouter from './routes/monitors.js' import mihomeRouter from './routes/mihome.js' +import checkinRouter from './routes/checkin.js' +import checklistsRouter from './routes/checklists.js' +import projectsRouter, { columnRouter } from './routes/projects.js' +import tasksRouter from './routes/tasks.js' +import powersyncRouter from './routes/powersync.js' const PORT = Number(process.env.PORT) || 3001 const DIST_DIR = path.join(process.cwd(), 'dist') @@ -29,6 +34,8 @@ app.use('/api/auth', authRouter) // OTA 接口是公开的 (检查/下载更新不需要登录), 必须挂载在带 authRequired 的 router 之前, // 否则 /api/ota/* 请求会先进入 dataRouter 等并被 authRequired 以 401 拦截 app.use('/api', otaRouter) +// PowerSync: token 签发 + 离线上行队列 (自带 authRequired, 同样必须挂在带 authRequired 的业务 router 之前) +app.use('/api/powersync', powersyncRouter) app.use('/api', dataRouter) app.use('/api', newsRouter) app.use('/api', alertsRouter) @@ -36,6 +43,13 @@ app.use('/api', articlesRouter) app.use('/api', apizeroRouter) app.use('/api', monitorsRouter) app.use('/api/mihome', mihomeRouter) +// V0.2 打卡 / 日历 / 清单 +app.use('/api/checkin', checkinRouter) +app.use('/api/checklists', checklistsRouter) +// V0.2 任务模块: 项目 / 列 / 任务 +app.use('/api/projects', projectsRouter) +app.use('/api/columns', columnRouter) +app.use('/api', tasksRouter) // 静态资源 (Vite build 产物) if (HAS_DIST) { @@ -55,11 +69,13 @@ app.use((err, _req, res, _next) => { res.status(500).json({ error: err.message || '服务器内部错误' }) }) -// 启动定时任务: 每天凌晨 3 点拉取新闻 -import('./cron.js').catch((e) => console.error('[cron] 加载失败:', e.message)) +// 启动定时任务: 每天凌晨 3 点拉取新闻; 0.2.1 验证部署以 DISABLE_CRON=1 关闭 (避免重复外呼) +if (process.env.DISABLE_CRON !== '1') { + import('./cron.js').catch((e) => console.error('[cron] 加载失败:', e.message)) -// 米家环境监控定时调度 (独立注册, 不与 cron.js 绑定, 见 server/mihome/scheduler.js) -import('./mihome/scheduler.js').catch((e) => console.error('[cron] 米家调度注册失败:', e.message)) + // 米家环境监控定时调度 (独立注册, 不与 cron.js 绑定, 见 server/mihome/scheduler.js) + import('./mihome/scheduler.js').catch((e) => console.error('[cron] 米家调度注册失败:', e.message)) +} app.listen(PORT, () => { console.log(`[iboard-api] listening on http://127.0.0.1:${PORT}`) diff --git a/server/mihome/client.js b/server/mihome/client.js index 201d9c0..f9edf13 100644 --- a/server/mihome/client.js +++ b/server/mihome/client.js @@ -91,8 +91,13 @@ export function getQrImageUrl() { } export async function startQrLogin() { - // 已有一轮在跑就直接复用: 同一张二维码继续有效, 不重复开 - if (qrState.running) return getQrState() + // 已有一轮在跑就直接复用: 同一张二维码继续有效, 不重复开. + // 注意: 这里绝不能提前 return 而不清 done —— 上一轮成功时 done=true 会残留, + // 前端第一次轮询就拿到 {done:true, running:false}, 立刻误判成"扫码成功"关掉弹窗. + if (qrState.running) { + const snap = getQrState() + return { ...snap, done: false, running: true } + } const api = getApi() qrState.running = true qrState.done = false diff --git a/server/routes/checkin.js b/server/routes/checkin.js new file mode 100644 index 0000000..5a28016 --- /dev/null +++ b/server/routes/checkin.js @@ -0,0 +1,345 @@ +import { Router } from 'express' +import { prisma } from '../db.js' +import { authRequired } from '../middleware.js' +import { + toDateStr, addDays, periodRange, periodDays, expectedPerDay, + periodIsClosed, eachDay, PERIODS, PERIOD_LABEL, +} from '../checkin/period.js' + +const router = Router() +router.use(authRequired) + +// 补打卡允许的回溯天数 (F-CK-10) +const BACKFILL_DAYS = 7 + +const DATE_RE = /^\d{4}-\d{2}-\d{2}$/ +const MONTH_RE = /^\d{4}-\d{2}$/ + +function normDate(v) { + const s = String(v || '') + return DATE_RE.test(s) ? s : toDateStr() +} + +// ---------- 统计辅助 ---------- + +// 记录 -> { habitId: { date: count } } +function groupRecords(records) { + const map = new Map() + for (const r of records) { + if (!map.has(r.habitId)) map.set(r.habitId, new Map()) + const byDate = map.get(r.habitId) + byDate.set(r.date, (byDate.get(r.date) || 0) + 1) + } + return map +} + +function countInRange(byDate, start, end) { + let n = 0 + eachDay(start, end, (d) => { n += byDate.get(d) || 0 }) + return n +} + +// 连续达标的周期数: 当前周期未结束且未达标 -> 跳过(不算断签), 不是中断 +function computeStreak(habit, byDate, today) { + let cursor = today + let streak = 0 + for (let i = 0; i < 400; i++) { + const { start } = periodRange(habit.period, cursor) + const done = countInRange(byDate, start, periodRange(habit.period, cursor).end) + if (done >= habit.target) { + streak++ + } else if (periodIsClosed(habit.period, cursor, today)) { + break + } + const prevEnd = addDays(start, -1) + if (prevEnd < '1970-01-01') break + cursor = prevEnd + } + return streak +} + +async function loadActiveHabits(userId) { + return prisma.habit.findMany({ + where: { userId, archived: false }, + orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }], + }) +} + +// 组装单个打卡项的展示态 +function buildItem(habit, byDate, date, today) { + const { start, end } = periodRange(habit.period, date) + const done = countInRange(byDate, start, end) + const target = Math.max(1, habit.target) + const onDate = byDate.get(date) || 0 + const expPerDay = expectedPerDay(habit.period, target, date) + return { + id: habit.id, + name: habit.name, + period: habit.period, + sortOrder: habit.sortOrder, + periodLabel: PERIOD_LABEL[habit.period] || '每天', + target, + color: habit.color, + icon: habit.icon, + note: habit.note, + archived: habit.archived, + periodStart: start, + periodEnd: end, + periodDays: periodDays(habit.period, date), + done, + remaining: Math.max(0, target - done), + percent: Math.min(1, target > 0 ? done / target : 1), + completed: done >= target, + over: Math.max(0, done - target), + countOnDate: onDate, + expectedPerDay: expPerDay, + dayDone: onDate >= expPerDay, + streak: computeStreak(habit, byDate, today), + } +} + +// 拉取 [start, end] 内的打卡记录并分组 +async function loadRecords(userId, habits, ranges) { + let min = ranges[0]?.start + let max = ranges[0]?.end + for (const r of ranges) { + if (r.start < min) min = r.start + if (r.end > max) max = r.end + } + if (!min || !max) return new Map() + const records = await prisma.checkIn.findMany({ + where: { userId, habitId: { in: habits.map((h) => h.id) }, date: { gte: min, lte: max } }, + select: { habitId: true, date: true }, + }) + return groupRecords(records) +} + +// ---------- 打卡项 ---------- + +// GET /api/checkin/habits?date=YYYY-MM-DD 今日(或指定日)待办 + 周期进度 +router.get('/habits', async (req, res, next) => { + try { + const userId = req.user.sub + const today = toDateStr() + const date = normDate(req.query.date) + const habits = await loadActiveHabits(userId) + const ranges = habits.map((h) => periodRange(h.period, date)) + const grouped = await loadRecords(userId, habits, ranges.length ? ranges : [{ start: date, end: date }]) + const items = habits.map((h) => buildItem(h, grouped.get(h.id) || new Map(), date, today)) + const doneCount = items.filter((i) => i.completed).length + res.json({ today, date, items, summary: { total: items.length, done: doneCount } }) + } catch (e) { next(e) } +}) + +// POST /api/checkin/habits 新建打卡项 +router.post('/habits', async (req, res, next) => { + try { + const userId = req.user.sub + const { name, period = 'day', target = 1, color = '#1677ff', icon = '', note = '' } = req.body || {} + const cleanName = String(name || '').trim() + if (!cleanName) return res.status(400).json({ error: '名称不能为空' }) + if (cleanName.length > 20) return res.status(400).json({ error: '名称不能超过 20 个字' }) + if (!PERIODS.includes(period)) return res.status(400).json({ error: '周期类型不合法' }) + const t = Math.floor(Number(target)) + if (!Number.isFinite(t) || t < 1) return res.status(400).json({ error: '目标次数需为 ≥1 的整数' }) + const last = await prisma.habit.findFirst({ where: { userId }, orderBy: { sortOrder: 'desc' }, select: { sortOrder: true } }) + const row = await prisma.habit.create({ + data: { userId, name: cleanName, period, target: t, color: String(color), icon: String(icon), note: String(note || ''), sortOrder: (last?.sortOrder ?? -1) + 1 }, + }) + res.json({ item: row }) + } catch (e) { next(e) } +}) + +// PUT /api/checkin/habits/:id 编辑 +router.put('/habits/:id', async (req, res, next) => { + try { + const userId = req.user.sub + const id = Number(req.params.id) + const exist = await prisma.habit.findFirst({ where: { id, userId } }) + if (!exist) return res.status(404).json({ error: '打卡项不存在' }) + const { name, period, target, color, icon, note, archived, sortOrder } = req.body || {} + const data = {} + if (name !== undefined) { + const n = String(name).trim() + if (!n) return res.status(400).json({ error: '名称不能为空' }) + if (n.length > 20) return res.status(400).json({ error: '名称不能超过 20 个字' }) + data.name = n + } + if (period !== undefined) { + if (!PERIODS.includes(period)) return res.status(400).json({ error: '周期类型不合法' }) + data.period = period + } + if (target !== undefined) { + const t = Math.floor(Number(target)) + if (!Number.isFinite(t) || t < 1) return res.status(400).json({ error: '目标次数需为 ≥1 的整数' }) + data.target = t + } + if (color !== undefined) data.color = String(color) + if (icon !== undefined) data.icon = String(icon) + if (note !== undefined) data.note = String(note) + if (archived !== undefined) data.archived = !!archived + if (sortOrder !== undefined) data.sortOrder = Number(sortOrder) || 0 + const row = await prisma.habit.update({ where: { id }, data }) + res.json({ item: row }) + } catch (e) { next(e) } +}) + +// POST /api/checkin/habits/reorder { ids: [习惯id 的新顺序] } 按数组下标重写 sortOrder +router.post('/habits/reorder', async (req, res, next) => { + try { + const userId = req.user.sub + const ids = Array.isArray((req.body || {}).ids) ? req.body.ids.map(Number).filter(Number.isFinite) : [] + if (!ids.length) return res.status(400).json({ error: 'ids 不能为空' }) + const habits = await prisma.habit.findMany({ where: { userId, id: { in: ids } } }) + if (habits.length !== new Set(ids).size) return res.status(400).json({ error: '包含无效的打卡项' }) + await prisma.$transaction( + ids.map((id, i) => prisma.habit.update({ where: { id }, data: { sortOrder: i } })), + ) + res.json({ ok: true }) + } catch (e) { next(e) } +}) + +// DELETE /api/checkin/habits/:id?hard=1 默认归档(保留历史), hard=1 物理删除 +router.delete('/habits/:id', async (req, res, next) => { + try { + const userId = req.user.sub + const id = Number(req.params.id) + const exist = await prisma.habit.findFirst({ where: { id, userId } }) + if (!exist) return res.status(404).json({ error: '打卡项不存在' }) + if (req.query.hard === '1') { + await prisma.checkIn.deleteMany({ where: { habitId: id } }) + await prisma.habit.delete({ where: { id } }) + return res.json({ ok: true, removed: true }) + } + const row = await prisma.habit.update({ where: { id }, data: { archived: true } }) + res.json({ ok: true, removed: false, item: row }) + } catch (e) { next(e) } +}) + +// ---------- 打卡 / 撤销 ---------- + +// POST /api/checkin/habits/:id/check { date?, note? } 打一次卡 +router.post('/habits/:id/check', async (req, res, next) => { + try { + const userId = req.user.sub + const id = Number(req.params.id) + const habit = await prisma.habit.findFirst({ where: { id, userId } }) + if (!habit) return res.status(404).json({ error: '打卡项不存在' }) + const today = toDateStr() + const date = normDate((req.body || {}).date) + if (date > today) return res.status(400).json({ error: '不能为未来日期打卡' }) + if (date < addDays(today, -BACKFILL_DAYS)) { + return res.status(400).json({ error: `最多只能补打最近 ${BACKFILL_DAYS} 天` }) + } + const dayRecords = await prisma.checkIn.findMany({ where: { habitId: id, date }, select: { seq: true } }) + const seq = dayRecords.reduce((m, r) => Math.max(m, r.seq), 0) + 1 + const note = String((req.body || {}).note || '').slice(0, 200) + await prisma.checkIn.create({ + data: { habitId: id, userId, date, seq, note, source: date === today ? 'user' : 'backfill' }, + }) + const grouped = await loadRecords(userId, [habit], [periodRange(habit.period, date)]) + res.json({ item: buildItem(habit, grouped.get(habit.id) || new Map(), date, today), date }) + } catch (e) { + // 唯一约束冲突 = 并发重复提交, 视为已打卡 + if (e && e.code === 'P2002') return res.status(409).json({ error: '该次打卡已记录' }) + next(e) + } +}) + +// DELETE /api/checkin/habits/:id/check?date= 撤销本周期内最近一次打卡 +router.delete('/habits/:id/check', async (req, res, next) => { + try { + const userId = req.user.sub + const id = Number(req.params.id) + const habit = await prisma.habit.findFirst({ where: { id, userId } }) + if (!habit) return res.status(404).json({ error: '打卡项不存在' }) + const today = toDateStr() + const date = normDate(req.query.date) + const { start, end } = periodRange(habit.period, date) + const last = await prisma.checkIn.findFirst({ + where: { habitId: id, date: { gte: start, lte: end } }, + orderBy: [{ date: 'desc' }, { seq: 'desc' }], + }) + if (!last) return res.status(400).json({ error: '本周期还没有打卡记录' }) + await prisma.checkIn.delete({ where: { id: last.id } }) + const grouped = await loadRecords(userId, [habit], [{ start, end }]) + res.json({ item: buildItem(habit, grouped.get(habit.id) || new Map(), date, today), date }) + } catch (e) { next(e) } +}) + +// ---------- 某日详情 / 月历 ---------- + +// GET /api/checkin/day?date=YYYY-MM-DD 当天每个打卡项的明细 (含可打卡/只读标记) +router.get('/day', async (req, res, next) => { + try { + const userId = req.user.sub + const today = toDateStr() + const date = normDate(req.query.date) + const habits = await loadActiveHabits(userId) + const ranges = habits.map((h) => periodRange(h.period, date)) + const grouped = await loadRecords(userId, habits, ranges.length ? ranges : [{ start: date, end: date }]) + const items = habits.map((h) => { + const base = buildItem(h, grouped.get(h.id) || new Map(), date, today) + return { ...base, canCheck: date <= today && date >= addDays(today, -BACKFILL_DAYS) } + }) + res.json({ today, date, isToday: date === today, isFuture: date > today, items }) + } catch (e) { next(e) } +}) + +// GET /api/checkin/calendar?month=YYYY-MM 月历: 每天完成情况 + 当月完成率 +router.get('/calendar', async (req, res, next) => { + try { + const userId = req.user.sub + const today = toDateStr() + const month = MONTH_RE.test(String(req.query.month || '')) ? String(req.query.month) : today.slice(0, 7) + const start = month + '-01' + const end = addDays(monthStartNext(month), -1) + const habits = await prisma.habit.findMany({ where: { userId }, orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }] }) + const active = habits.filter((h) => !h.archived) + const records = await prisma.checkIn.findMany({ + where: { userId, date: { gte: start, lte: end } }, + select: { habitId: true, date: true }, + }) + const grouped = groupRecords(records) + const days = [] + let expTotal = 0 + let doneTotal = 0 + eachDay(start, end, (d) => { + let expected = 0 + let actual = 0 + let over = false + const detail = [] + for (const h of active) { + if (toDateStr(h.createdAt) > d) continue // 创建之前的日期不计入期望 + const exp = expectedPerDay(h.period, Math.max(1, h.target), d) + const cnt = (grouped.get(h.id) || new Map()).get(d) || 0 + expected += exp + actual += Math.min(cnt, exp) + if (cnt > exp) over = true + detail.push({ id: h.id, name: h.name, color: h.color, count: cnt, expected: exp, done: cnt >= exp }) + } + const isFuture = d > today + if (!isFuture) { expTotal += expected; doneTotal += actual } + let status = 'empty' + if (expected > 0 && actual >= expected) status = over ? 'over' : 'full' + else if (actual > 0) status = 'partial' + days.push({ + date: d, expected, actual, status, isToday: d === today, isFuture, + habits: detail, + }) + }) + res.json({ + today, month, days, + rate: expTotal > 0 ? Math.min(1, doneTotal / expTotal) : 0, + habits: active.map((h) => ({ id: h.id, name: h.name, color: h.color, period: h.period, target: h.target })), + }) + } catch (e) { next(e) } +}) + +function monthStartNext(month) { + const [y, m] = month.split('-').map(Number) + const d = new Date(Date.UTC(y, m, 1)) // m 已 +1 (下月) + return d.toISOString().slice(0, 10) +} + +export default router diff --git a/server/routes/checklists.js b/server/routes/checklists.js new file mode 100644 index 0000000..76e3558 --- /dev/null +++ b/server/routes/checklists.js @@ -0,0 +1,356 @@ +import { Router } from 'express' +import { prisma } from '../db.js' +import { authRequired } from '../middleware.js' + +const router = Router() +router.use(authRequired) + +// 系统预置模板 (builtin): 首次访问模板接口时幂等写入 +const BUILTIN_TEMPLATES = [ + { + name: '出差', + scene: '出差', + items: [ + { title: '身份证 / 证件' }, + { title: '手机充电器 + 充电宝' }, + { title: '笔记本电脑 + 电源' }, + { title: '耳机' }, + { title: '换洗衣物' }, + { title: '洗漱包' }, + { title: '常用药' }, + { title: '雨伞' }, + { title: '车票 / 机票 / 酒店确认' }, + { title: '工牌 / 名片' }, + { title: '报销单据' }, + ], + }, + { + name: '回老家', + scene: '回老家', + items: [ + { title: '身份证 / 证件' }, + { title: '钥匙' }, + { title: '手机充电器 + 充电宝' }, + { title: '换洗衣物' }, + { title: '洗漱包' }, + { title: '常用药' }, + { title: '给家人的礼物 / 伴手礼' }, + { title: '长辈的药品 / 营养品' }, + { title: '口罩' }, + { title: '现金零钱' }, + { title: '车票 / 加油 / 行程确认' }, + { title: '家里门窗水电检查' }, + ], + }, +] + +async function ensureBuiltin() { + const count = await prisma.checkListTemplate.count({ where: { builtin: true } }) + if (count >= BUILTIN_TEMPLATES.length) return + for (const t of BUILTIN_TEMPLATES) { + const exist = await prisma.checkListTemplate.findFirst({ where: { builtin: true, name: t.name } }) + if (exist) continue + await prisma.checkListTemplate.create({ + data: { name: t.name, scene: t.scene, items: t.items, builtin: true, userId: null }, + }) + } +} + +const LIST_INCLUDE = { items: { orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }] } } + +function withProgress(list) { + const items = list.items || [] + const done = items.filter((i) => i.done).length + return { + ...list, + items, + total: items.length, + done, + percent: items.length > 0 ? done / items.length : 0, + completed: items.length > 0 && done === items.length, + } +} + +// ---------- 模板 ---------- + +// 模板按名称字母顺序 (中文按拼音) 排序, 前后端共用同一比较器语义 +const NAME_COLLATOR = new Intl.Collator('zh-Hans-CN', { numeric: true, sensitivity: 'base' }) +const byName = (a, b) => NAME_COLLATOR.compare(String(a.name || ''), String(b.name || '')) + +// GET /api/checklists/templates 模板列表 (含系统预置, 按字母顺序) +router.get('/templates', async (_req, res, next) => { + try { + await ensureBuiltin() + const rows = await prisma.checkListTemplate.findMany({ + where: { OR: [{ builtin: true }, { userId: _req.user.sub }] }, + }) + res.json({ items: rows.sort(byName) }) + } catch (e) { next(e) } +}) + +// GET /api/checklists/templates/:id 模板详情 +router.get('/templates/:id', async (req, res, next) => { + try { + const row = await prisma.checkListTemplate.findFirst({ + where: { id: Number(req.params.id), OR: [{ builtin: true }, { userId: req.user.sub }] }, + }) + if (!row) return res.status(404).json({ error: '模板不存在' }) + res.json({ item: row }) + } catch (e) { next(e) } +}) + +// POST /api/checklists/templates 另存为模板 { name, scene?, items: [{title, note}] } +router.post('/templates', async (req, res, next) => { + try { + const userId = req.user.sub + const { name, scene = '', items = [] } = req.body || {} + const cleanName = String(name || '').trim() + if (!cleanName) return res.status(400).json({ error: '模板名称不能为空' }) + if (cleanName.length > 20) return res.status(400).json({ error: '模板名称不能超过 20 个字' }) + const list = (Array.isArray(items) ? items : []) + .map((i) => ({ title: String(i?.title || '').trim(), note: String(i?.note || '') })) + .filter((i) => i.title) + if (!list.length) return res.status(400).json({ error: '模板至少需要一个检查项' }) + const row = await prisma.checkListTemplate.create({ + data: { userId, name: cleanName, scene: String(scene), items: list, builtin: false }, + }) + res.json({ item: row }) + } catch (e) { next(e) } +}) + +// PUT /api/checklists/templates/:id 改名称 / 检查项 (系统预置模板不可改) +router.put('/templates/:id', async (req, res, next) => { + try { + const userId = req.user.sub + const id = Number(req.params.id) + const exist = await prisma.checkListTemplate.findFirst({ where: { id, userId, builtin: false } }) + if (!exist) return res.status(404).json({ error: '模板不存在、无权限修改或是系统预置模板' }) + const { name, scene, items } = req.body || {} + const data = {} + if (name !== undefined) { + const n = String(name).trim() + if (!n) return res.status(400).json({ error: '模板名称不能为空' }) + if (n.length > 20) return res.status(400).json({ error: '模板名称不能超过 20 个字' }) + data.name = n + } + if (scene !== undefined) data.scene = String(scene) + if (items !== undefined) { + const list = (Array.isArray(items) ? items : []) + .map((i) => ({ title: String(i?.title || '').trim(), note: String(i?.note || '') })) + .filter((i) => i.title) + if (!list.length) return res.status(400).json({ error: '模板至少需要一个检查项' }) + data.items = list + } + const row = await prisma.checkListTemplate.update({ where: { id }, data }) + res.json({ item: row }) + } catch (e) { next(e) } +}) + +// DELETE /api/checklists/templates/:id 仅自建模板可删 +router.delete('/templates/:id', async (req, res, next) => { + try { + const userId = req.user.sub + const id = Number(req.params.id) + const exist = await prisma.checkListTemplate.findFirst({ where: { id, userId, builtin: false } }) + if (!exist) return res.status(404).json({ error: '模板不存在或无权删除' }) + await prisma.checkListTemplate.delete({ where: { id } }) + res.json({ ok: true }) + } catch (e) { next(e) } +}) + +// ---------- 清单 ---------- + +// GET /api/checklists?archived=0|1 +router.get('/', async (req, res, next) => { + try { + const userId = req.user.sub + const archived = String(req.query.archived || '0') === '1' + const rows = await prisma.checkList.findMany({ + where: { userId, archived }, + include: LIST_INCLUDE, + orderBy: [{ updatedAt: 'desc' }], + }) + res.json({ items: rows.map(withProgress) }) + } catch (e) { next(e) } +}) + +// POST /api/checklists { title, scene?, note?, items?: string[] | [{title,note}], templateId? } +router.post('/', async (req, res, next) => { + try { + const userId = req.user.sub + const { title, scene = '', note = '', items = [], templateId } = req.body || {} + const cleanTitle = String(title || '').trim() + if (!cleanTitle) return res.status(400).json({ error: '清单名称不能为空' }) + + let seed = (Array.isArray(items) ? items : []) + .map((i) => (typeof i === 'string' ? { title: i } : i)) + .map((i) => ({ title: String(i?.title || '').trim(), note: String(i?.note || '') })) + .filter((i) => i.title) + + if (!seed.length && templateId) { + const tpl = await prisma.checkListTemplate.findFirst({ + where: { id: Number(templateId), OR: [{ builtin: true }, { userId }] }, + }) + if (tpl && Array.isArray(tpl.items)) { + seed = tpl.items.map((i) => ({ title: String(i?.title || '').trim(), note: String(i?.note || '') })).filter((i) => i.title) + } + } + + const row = await prisma.checkList.create({ + data: { + userId, + title: cleanTitle, + scene: String(scene), + note: String(note), + items: { create: seed.map((i, idx) => ({ title: i.title, note: i.note, sortOrder: idx })) }, + }, + include: LIST_INCLUDE, + }) + res.json({ item: withProgress(row) }) + } catch (e) { next(e) } +}) + +// GET /api/checklists/:id +router.get('/:id', async (req, res, next) => { + try { + const userId = req.user.sub + const id = Number(req.params.id) + const row = await prisma.checkList.findFirst({ where: { id, userId }, include: LIST_INCLUDE }) + if (!row) return res.status(404).json({ error: '清单不存在' }) + res.json({ item: withProgress(row) }) + } catch (e) { next(e) } +}) + +// PUT /api/checklists/:id { title?, scene?, note?, archived? } +router.put('/:id', async (req, res, next) => { + try { + const userId = req.user.sub + const id = Number(req.params.id) + const exist = await prisma.checkList.findFirst({ where: { id, userId } }) + if (!exist) return res.status(404).json({ error: '清单不存在' }) + const { title, scene, note, archived } = req.body || {} + const data = {} + if (title !== undefined) { + const t = String(title).trim() + if (!t) return res.status(400).json({ error: '清单名称不能为空' }) + data.title = t + } + if (scene !== undefined) data.scene = String(scene) + if (note !== undefined) data.note = String(note) + if (archived !== undefined) data.archived = !!archived + const row = await prisma.checkList.update({ where: { id }, data, include: LIST_INCLUDE }) + res.json({ item: withProgress(row) }) + } catch (e) { next(e) } +}) + +// DELETE /api/checklists/:id +router.delete('/:id', async (req, res, next) => { + try { + const userId = req.user.sub + const id = Number(req.params.id) + const exist = await prisma.checkList.findFirst({ where: { id, userId } }) + if (!exist) return res.status(404).json({ error: '清单不存在' }) + await prisma.checkListItem.deleteMany({ where: { listId: id } }) + await prisma.checkList.delete({ where: { id } }) + res.json({ ok: true }) + } catch (e) { next(e) } +}) + +// POST /api/checklists/:id/duplicate 复制一份新清单 (勾选状态重置) +router.post('/:id/duplicate', async (req, res, next) => { + try { + const userId = req.user.sub + const id = Number(req.params.id) + const src = await prisma.checkList.findFirst({ where: { id, userId }, include: LIST_INCLUDE }) + if (!src) return res.status(404).json({ error: '清单不存在' }) + const row = await prisma.checkList.create({ + data: { + userId, + title: src.title + ' 副本', + scene: src.scene, + note: src.note, + items: { + create: src.items.map((i, idx) => ({ title: i.title, note: i.note, done: false, sortOrder: idx })), + }, + }, + include: LIST_INCLUDE, + }) + res.json({ item: withProgress(row) }) + } catch (e) { next(e) } +}) + +// ---------- 检查项 ---------- + +// POST /api/checklists/:id/items { title, note? } +router.post('/:id/items', async (req, res, next) => { + try { + const userId = req.user.sub + const listId = Number(req.params.id) + const list = await prisma.checkList.findFirst({ where: { id: listId, userId } }) + if (!list) return res.status(404).json({ error: '清单不存在' }) + const title = String((req.body || {}).title || '').trim() + if (!title) return res.status(400).json({ error: '检查项不能为空' }) + if (title.length > 40) return res.status(400).json({ error: '检查项不能超过 40 个字' }) + const max = await prisma.checkListItem.findFirst({ where: { listId }, orderBy: { sortOrder: 'desc' } }) + const row = await prisma.checkListItem.create({ + data: { listId, title, note: String((req.body || {}).note || ''), sortOrder: (max?.sortOrder || 0) + 1 }, + }) + res.json({ item: row }) + } catch (e) { next(e) } +}) + +// PUT /api/checklists/items/:id { title?, note?, done?, sortOrder? } +router.put('/items/:id', async (req, res, next) => { + try { + const userId = req.user.sub + const id = Number(req.params.id) + const exist = await prisma.checkListItem.findFirst({ where: { id, list: { userId } } }) + if (!exist) return res.status(404).json({ error: '检查项不存在' }) + const { title, note, done, sortOrder } = req.body || {} + const data = {} + if (title !== undefined) { + const t = String(title).trim() + if (!t) return res.status(400).json({ error: '检查项不能为空' }) + data.title = t + } + if (note !== undefined) data.note = String(note) + if (sortOrder !== undefined) data.sortOrder = Number(sortOrder) || 0 + if (done !== undefined) { + data.done = !!done + data.doneAt = done ? new Date() : null + } + const row = await prisma.checkListItem.update({ where: { id }, data }) + res.json({ item: row }) + } catch (e) { next(e) } +}) + +// DELETE /api/checklists/items/:id +router.delete('/items/:id', async (req, res, next) => { + try { + const userId = req.user.sub + const id = Number(req.params.id) + const exist = await prisma.checkListItem.findFirst({ where: { id, list: { userId } } }) + if (!exist) return res.status(404).json({ error: '检查项不存在' }) + await prisma.checkListItem.delete({ where: { id } }) + res.json({ ok: true }) + } catch (e) { next(e) } +}) + +// POST /api/checklists/:id/check-all { done: boolean } 一键全选 / 全不选 +router.post('/:id/check-all', async (req, res, next) => { + try { + const userId = req.user.sub + const listId = Number(req.params.id) + const list = await prisma.checkList.findFirst({ where: { id: listId, userId } }) + if (!list) return res.status(404).json({ error: '清单不存在' }) + const done = !!(req.body || {}).done + await prisma.checkListItem.updateMany({ + where: { listId }, + data: { done, doneAt: done ? new Date() : null }, + }) + const row = await prisma.checkList.findFirst({ where: { id: listId }, include: LIST_INCLUDE }) + res.json({ item: withProgress(row) }) + } catch (e) { next(e) } +}) + +export default router diff --git a/server/routes/ota.js b/server/routes/ota.js index 1242a49..21e92ff 100644 --- a/server/routes/ota.js +++ b/server/routes/ota.js @@ -1,102 +1,105 @@ -// OTA 升级接口 (兼容 tauri-plugin-hotswap 服务端约定) -// GET /api/ota/check/:current_sequence -// - 客户端传当前 sequence, 服务端判断是否有更新 -// - 有: 200 + manifest JSON -// - 无: 204 No Content -// GET /api/ota/bundle/:filename -// - 下载前端 bundle (tar.gz), 供 manifest.url 引用 -// GET /api/ota/manifest-web -// - 给 Web 浏览器用的简化版 manifest (无签名, 仅展示用) -// -// 数据源: 环境变量 OTA_PACKAGE_DIR 指向的目录, 里面要放: -// - latest.json (由 scripts/build-ota.mjs 生成) -// { sequence, version, min_binary_version, signature, notes, bundle, mandatory, pub_date, bundle_size } -// - (tar.gz 升级包) -import { Router } from 'express' -import fs from 'node:fs' -import path from 'node:path' - -const router = Router() - -function resolveOtaDir() { - const dir = process.env.OTA_PACKAGE_DIR - ? path.resolve(process.env.OTA_PACKAGE_DIR) - : path.resolve(process.cwd(), 'ota-package') - return dir -} - -function readLatest() { - const p = path.join(resolveOtaDir(), 'latest.json') - if (!fs.existsSync(p)) return null - try { - return JSON.parse(fs.readFileSync(p, 'utf8')) - } catch (e) { - console.error('[ota] latest.json 解析失败:', e.message) - return null - } -} - -// GET /api/ota/check/:current_sequence -// 客户端实际请求 URL 是 endpoint 中 {{current_sequence}} 替换后的形式 -router.get('/ota/check/:current_sequence', (req, res) => { - const currentSeq = Number(req.params.current_sequence) - if (!Number.isFinite(currentSeq) || currentSeq < 0) { - return res.status(400).json({ error: '无效的 sequence' }) - } - const latest = readLatest() - if (!latest) { - // 无可用包, 按约定返回 204 - return res.status(204).end() - } - // 不新于当前则 204 - if (typeof latest.sequence === 'number' && latest.sequence <= currentSeq) { - return res.status(204).end() - } - // 检查 binary_version 兼容性 (客户端会再校验一次, 这里只做参考过滤) - // 客户端传 binary_version query: 暂不强制, 让客户端兜底 - const dir = resolveOtaDir() - const base = process.env.OTA_PUBLIC_BASE || '' - res.json({ - version: latest.version, - sequence: latest.sequence, - min_binary_version: latest.min_binary_version || '0.0.0', - url: (base ? base : '') + '/api/ota/bundle/' + encodeURIComponent(latest.bundle), - signature: latest.signature, - notes: latest.notes || '', - pub_date: latest.pub_date || new Date().toISOString(), - mandatory: !!latest.mandatory, - bundle_size: latest.bundle_size || 0, - }) -}) - -// GET /api/ota/bundle/:filename - 流式下载 bundle -router.get('/ota/bundle/:filename', (req, res) => { - const dir = resolveOtaDir() - // 防越界: 只取 basename - const name = path.basename(req.params.filename) - const p = path.join(dir, name) - if (!fs.existsSync(p)) return res.status(404).json({ error: 'bundle 不存在' }) - const stat = fs.statSync(p) - res.setHeader('Content-Type', 'application/gzip') - res.setHeader('Content-Length', stat.size) - res.setHeader('Content-Disposition', 'attachment; filename="' + name + '"') - fs.createReadStream(p).pipe(res) -}) - -// GET /api/ota/manifest-web - Web 浏览器用的简化版 -// 客户端会拿这个跟当前 build_id 比较, 有新版本就提示用户刷新 -router.get('/ota/manifest-web', (_req, res) => { - const latest = readLatest() - if (!latest) return res.json({ available: false }) - res.json({ - available: true, - version: latest.version, - build_id: latest.build_id || latest.version, - note: latest.notes || '', - size: latest.bundle_size || 0, - }) -}) - -// GET /api/ota/info 已删除: 诊断接口会暴露服务器路径与签名, 且无调用方 - -export default router +// OTA 升级接口 (兼容 tauri-plugin-hotswap 服务端约定) +// GET /api/ota/check/:current_sequence +// - 客户端传当前 sequence, 服务端判断是否有更新 +// - 有: 200 + manifest JSON +// - 无: 204 No Content +// GET /api/ota/bundle/:filename +// - 下载前端 bundle (tar.gz), 供 manifest.url 引用 +// GET /api/ota/manifest-web +// - 给 Web 浏览器用的简化版 manifest (无签名, 仅展示用) +// +// 数据源: 环境变量 OTA_PACKAGE_DIR 指向的目录, 里面要放: +// - latest.json (由 scripts/build-ota.mjs 生成) +// { sequence, version, min_binary_version, signature, notes, bundle, mandatory, pub_date, bundle_size } +// - (tar.gz 升级包) +import { Router } from 'express' +import fs from 'node:fs' +import path from 'node:path' + +const router = Router() + +function resolveOtaDir() { + const dir = process.env.OTA_PACKAGE_DIR + ? path.resolve(process.env.OTA_PACKAGE_DIR) + : path.resolve(process.cwd(), 'ota-package') + return dir +} + +function readLatest() { + const p = path.join(resolveOtaDir(), 'latest.json') + if (!fs.existsSync(p)) return null + try { + return JSON.parse(fs.readFileSync(p, 'utf8')) + } catch (e) { + console.error('[ota] latest.json 解析失败:', e.message) + return null + } +} + +// GET /api/ota/check/:current_sequence +// 客户端实际请求 URL 是 endpoint 中 {{current_sequence}} 替换后的形式 +router.get('/ota/check/:current_sequence', (req, res) => { + const currentSeq = Number(req.params.current_sequence) + if (!Number.isFinite(currentSeq) || currentSeq < 0) { + return res.status(400).json({ error: '无效的 sequence' }) + } + const latest = readLatest() + if (!latest) { + // 无可用包, 按约定返回 204 + return res.status(204).end() + } + // 不新于当前则 204 + if (typeof latest.sequence === 'number' && latest.sequence <= currentSeq) { + return res.status(204).end() + } + // 检查 binary_version 兼容性 (客户端会再校验一次, 这里只做参考过滤) + // 客户端传 binary_version query: 暂不强制, 让客户端兜底 + const dir = resolveOtaDir() + const base = process.env.OTA_PUBLIC_BASE || '' + res.json({ + version: latest.version, + sequence: latest.sequence, + min_binary_version: latest.min_binary_version || '0.0.0', + url: (base ? base : '') + '/api/ota/bundle/' + encodeURIComponent(latest.bundle), + signature: latest.signature, + notes: latest.notes || '', + commit: latest.commit || '', + pub_date: latest.pub_date || new Date().toISOString(), + mandatory: !!latest.mandatory, + bundle_size: latest.bundle_size || 0, + }) +}) + +// GET /api/ota/bundle/:filename - 流式下载 bundle +router.get('/ota/bundle/:filename', (req, res) => { + const dir = resolveOtaDir() + // 防越界: 只取 basename + const name = path.basename(req.params.filename) + const p = path.join(dir, name) + if (!fs.existsSync(p)) return res.status(404).json({ error: 'bundle 不存在' }) + const stat = fs.statSync(p) + res.setHeader('Content-Type', 'application/gzip') + res.setHeader('Content-Length', stat.size) + res.setHeader('Content-Disposition', 'attachment; filename="' + name + '"') + fs.createReadStream(p).pipe(res) +}) + +// GET /api/ota/manifest-web - Web 浏览器用的简化版 +// 客户端会拿这个跟当前 build_id 比较, 有新版本就提示用户刷新 +router.get('/ota/manifest-web', (_req, res) => { + const latest = readLatest() + if (!latest) return res.json({ available: false }) + res.json({ + available: true, + version: latest.version, + sequence: latest.sequence, + commit: latest.commit || '', + build_id: latest.build_id || latest.version, + note: latest.notes || '', + size: latest.bundle_size || 0, + }) +}) + +// GET /api/ota/info 已删除: 诊断接口会暴露服务器路径与签名, 且无调用方 + +export default router diff --git a/server/routes/powersync.js b/server/routes/powersync.js new file mode 100644 index 0000000..0a6b095 --- /dev/null +++ b/server/routes/powersync.js @@ -0,0 +1,386 @@ +// PowerSync 接入: 同步 token 签发 + 客户端上传队列落地 (0.2.1, 见 docs/V0.2/离线编辑模块/0.2.1实施方案.md) +// 下行由 powersync-service 经 PG 逻辑复制完成; 本文件只负责: +// 1. /token 为已登录用户签发 powersync-service 接受的 JWT (aud=powersync, 独立密钥可选) +// 2. /upload 批量落地客户端离线写操作, 按 uuid 幂等 upsert, 服务端统一分配编号 +import { Router } from 'express' +import jwt from 'jsonwebtoken' +import { prisma } from '../db.js' +import { JWT_SECRET, authRequired } from '../middleware.js' +import { genUuid, normProjectCode, allocProjectCode, withTaskNumber } from '../task-domain/identity.js' + +const router = Router() + +// ---------- 同步 token ---------- +router.post('/token', authRequired, async (req, res, next) => { + try { + const user = await prisma.user.findUnique({ + where: { id: req.user.sub }, + select: { id: true, name: true, email: true }, + }) + if (!user) return res.status(401).json({ error: '用户不存在' }) + const secret = process.env.POWERSYNC_JWT_SECRET || JWT_SECRET + const token = jwt.sign( + { sub: String(user.id), aud: 'powersync', name: user.name, email: user.email }, + secret, + { expiresIn: '12h' }, + ) + res.json({ token, endpoint: process.env.POWERSYNC_URL || '' }) + } catch (e) { next(e) } +}) + +// ---------- 上传队列 ---------- +// 契约: POST { ops: [{ id, table, op: 'PUT'|'DELETE', uuid, data }] } +// table ∈ Project | BoardColumn | ProjectMember | Task | TaskMember | TaskTag +// data 为客户端本地全量行 (列名与客户端 schema 一致); 外键列 (projectId/columnId/parentId/taskId) +// 的值即父行的本地 id = uuid, 服务端据此解析为内部自增 id +// 返回 { results: [{ id, ok, skipped?, error? }] }, 单条失败不影响同批其它 op +const TABLES = ['Project', 'BoardColumn', 'ProjectMember', 'Task', 'TaskMember', 'TaskTag'] +const TABLE_RANK = Object.fromEntries(TABLES.map((t, i) => [t, i])) + +const COLUMN_RETENTIONS = ['never', 'today', '3d', 'week', 'month', 'forever'] +const TASK_STATUSES = ['todo', 'doing', 'done'] +const AUTO_ARCHIVE_OPTIONS = ['3d', '1w', '1m', 'never'] + +function isoTime(v) { + if (!v) return null + const d = new Date(v) + return Number.isNaN(d.getTime()) ? null : d +} + +// LWW: 服务端已有行不早于客户端数据 => 跳过本次覆盖 +function isStale(existing, data) { + const t = isoTime(data?.updatedAt) + if (!t) return false + return existing.updatedAt && existing.updatedAt.getTime() >= t.getTime() +} + +function cleanExtras(v) { + if (!Array.isArray(v)) return [] + return v.slice(0, 50).map((e) => ({ + label: String(e?.label ?? '').slice(0, 40), + value: String(e?.value ?? ''), + url: e?.url ? String(e.url) : '', + })) +} + +function cleanNotes(v) { + if (!Array.isArray(v)) return [] + return v.slice(0, 50).map((n) => ({ + title: String(n?.title ?? '').slice(0, 50), + content: String(n?.content ?? '').slice(0, 20000), + })) +} + +async function applyPut(op, ctx) { + const { userId, assertMember } = ctx + const { table, uuid } = op + const d = op.data || {} + if (!uuid) throw new Error('缺少 uuid') + + if (table === 'Project') { + const existing = await prisma.project.findUnique({ where: { uuid } }) + if (existing) { + if (!(await assertMember(existing.id))) throw new Error('无项目权限') + if (isStale(existing, d)) return { skipped: true } + const data = {} + if (d.name !== undefined) { + const t = String(d.name).trim() + if (!t || t.length > 20) throw new Error('项目名称非法') + data.name = t + } + if (d.archived !== undefined) data.archived = !!d.archived + if (d.notes !== undefined) data.notes = cleanNotes(d.notes) + if (d.autoArchive !== undefined) { + data.autoArchive = AUTO_ARCHIVE_OPTIONS.includes(d.autoArchive) ? d.autoArchive : 'never' + } + if (d.taskKinds !== undefined) { + data.taskKinds = Array.isArray(d.taskKinds) ? d.taskKinds.map((k) => String(k).slice(0, 20)).filter(Boolean) : null + } + if (Object.keys(data).length) await prisma.project.update({ where: { uuid }, data }) + return {} + } + const name = String(d.name || '').trim() + if (!name || name.length > 20) throw new Error('项目名称非法') + const code = normProjectCode(d.code) || (await allocProjectCode(name)) + const row = await prisma.project.create({ + data: { uuid, code, name, ownerId: userId, isPersonal: !!d.isPersonal, archived: !!d.archived }, + }) + return { rowId: row.id, code: row.code } + } + + if (table === 'BoardColumn') { + const existing = await prisma.boardColumn.findUnique({ where: { uuid } }) + if (existing) { + if (!(await assertMember(existing.projectId))) throw new Error('无项目权限') + if (isStale(existing, d)) return { skipped: true } + const data = {} + if (d.name !== undefined) { + const t = String(d.name).trim() + if (!t || t.length > 20) throw new Error('列名非法') + data.name = t + } + if (d.sortOrder !== undefined) data.sortOrder = Number(d.sortOrder) || 0 + if (d.boundStatus !== undefined) data.boundStatus = TASK_STATUSES.includes(d.boundStatus) ? d.boundStatus : null + if (d.retention !== undefined && COLUMN_RETENTIONS.includes(d.retention)) data.retention = d.retention + if (Object.keys(data).length) await prisma.boardColumn.update({ where: { uuid }, data }) + return {} + } + const project = await prisma.project.findUnique({ where: { uuid: d.projectId } }) + if (!project || !(await assertMember(project.id))) throw new Error('项目不存在或无权限') + const name = String(d.name || '').trim() + if (!name || name.length > 20) throw new Error('列名非法') + await prisma.boardColumn.create({ + data: { + uuid, + projectId: project.id, + name, + sortOrder: Number(d.sortOrder) || 0, + boundStatus: TASK_STATUSES.includes(d.boundStatus) ? d.boundStatus : null, + retention: COLUMN_RETENTIONS.includes(d.retention) ? d.retention : 'forever', + }, + }) + return {} + } + + if (table === 'ProjectMember') { + const existing = await prisma.projectMember.findUnique({ where: { uuid } }) + if (existing) { + // 成员关系只增不改: 已存在即视为完成 (幂等) + return { skipped: true } + } + const project = await prisma.project.findUnique({ where: { uuid: d.projectId } }) + if (!project || !(await assertMember(project.id))) throw new Error('项目不存在或无权限') + const target = Number(d.userId) + if (!target) throw new Error('userId 非法') + const dup = await prisma.projectMember.findUnique({ + where: { projectId_userId: { projectId: project.id, userId: target } }, + }) + if (dup) return { skipped: true } + await prisma.projectMember.create({ + data: { uuid, projectId: project.id, userId: target, permission: d.permission === 'admin' ? 'admin' : 'member' }, + }) + return {} + } + + if (table === 'Task') { + const existing = await prisma.task.findUnique({ where: { uuid } }) + if (existing) { + if (!(await assertMember(existing.projectId))) throw new Error('无项目权限') + if (isStale(existing, d)) return { skipped: true } + const data = {} + if (d.title !== undefined) { + const t = String(d.title).trim() + if (!t || t.length > 30) throw new Error('任务标题非法') + data.title = t + } + if (d.description !== undefined) data.description = String(d.description || '') + if (d.kind !== undefined) data.kind = d.kind ? String(d.kind).trim().slice(0, 20) : null + if (d.markDate !== undefined) { + const m = isoTime(d.markDate) + if (d.markDate && !m) throw new Error('时间标记非法') + data.markDate = m + } + if (d.extras !== undefined) data.extras = cleanExtras(d.extras) + if (d.sortOrder !== undefined) data.sortOrder = Number(d.sortOrder) || 0 + if (d.assigneeId !== undefined) data.assigneeId = d.assigneeId ? Number(d.assigneeId) : null + if (d.doneAt !== undefined) data.doneAt = isoTime(d.doneAt) + // 归档状态 (T-ARC): 客户端本地归档/恢复走这里 + if (d.archived !== undefined) data.archived = !!d.archived + if (d.archivedAt !== undefined) data.archivedAt = isoTime(d.archivedAt) + if (d.archiveSource !== undefined) { + data.archiveSource = ['manual', 'auto'].includes(d.archiveSource) ? d.archiveSource : null + } + if (d.columnId !== undefined) { + const col = await prisma.boardColumn.findUnique({ where: { uuid: d.columnId } }) + if (!col || col.projectId !== existing.projectId) throw new Error('看板列不存在') + data.columnId = col.id + } + if (d.parentId !== undefined) { + if (!d.parentId) { + data.parentId = null + } else { + const parent = await prisma.task.findUnique({ where: { uuid: d.parentId } }) + if (!parent || parent.projectId !== existing.projectId) throw new Error('父任务不存在') + if (parent.parentId) throw new Error('仅支持一级子任务') + data.parentId = parent.id + } + } + if (Object.keys(data).length) await prisma.task.update({ where: { uuid }, data }) + return {} + } + // 离线新建: 服务端解析父引用并分配项目内编号 + const project = await prisma.project.findUnique({ where: { uuid: d.projectId } }) + if (!project || !(await assertMember(project.id))) throw new Error('项目不存在或无权限') + const title = String(d.title || '').trim() + if (!title || title.length > 30) throw new Error('任务标题非法') + let columnId = null + if (d.columnId) { + const col = await prisma.boardColumn.findUnique({ where: { uuid: d.columnId } }) + if (!col || col.projectId !== project.id) throw new Error('看板列不存在') + columnId = col.id + } else { + const first = await prisma.boardColumn.findFirst({ + where: { projectId: project.id }, + orderBy: { sortOrder: 'asc' }, + }) + if (!first) throw new Error('项目没有可用的看板列') + columnId = first.id + } + let parentId = null + if (d.parentId) { + const parent = await prisma.task.findUnique({ where: { uuid: d.parentId } }) + if (!parent || parent.projectId !== project.id) throw new Error('父任务不存在') + if (parent.parentId) throw new Error('仅支持一级子任务') + parentId = parent.id + } + const status = TASK_STATUSES.includes(d.status) ? d.status : 'todo' + const base = { + uuid, + projectId: project.id, + columnId, + parentId, + title, + description: String(d.description || ''), + kind: d.kind ? String(d.kind).trim().slice(0, 20) : null, + markDate: isoTime(d.markDate), + status, + assigneeId: d.assigneeId ? Number(d.assigneeId) : null, + creatorId: userId, + extras: cleanExtras(d.extras), + doneAt: status === 'done' ? (isoTime(d.doneAt) || new Date()) : null, + sortOrder: Number(d.sortOrder) || 0, + } + const { value: row } = await withTaskNumber(project.id, (number) => + prisma.task.create({ data: { ...base, number } }), + ) + return { rowId: row.id, number: row.number } + } + + if (table === 'TaskMember') { + const existing = await prisma.taskMember.findUnique({ where: { uuid } }) + if (existing) return { skipped: true } + const task = await prisma.task.findUnique({ where: { uuid: d.taskId } }) + if (!task || !(await assertMember(task.projectId))) throw new Error('任务不存在或无权限') + const uid = Number(d.userId) + if (!uid) throw new Error('userId 非法') + const dup = await prisma.taskMember.findUnique({ + where: { taskId_userId: { taskId: task.id, userId: uid } }, + }) + if (dup) return { skipped: true } + await prisma.taskMember.create({ data: { uuid, taskId: task.id, userId: uid } }) + return {} + } + + if (table === 'TaskTag') { + const existing = await prisma.taskTag.findUnique({ where: { uuid } }) + if (existing) return { skipped: true } + const task = await prisma.task.findUnique({ where: { uuid: d.taskId } }) + if (!task || !(await assertMember(task.projectId))) throw new Error('任务不存在或无权限') + const name = String(d.name || '').trim().slice(0, 20) + if (!name) throw new Error('标签名非法') + const dup = await prisma.taskTag.findFirst({ where: { taskId: task.id, name } }) + if (dup) return { skipped: true } + await prisma.taskTag.create({ data: { uuid, taskId: task.id, name } }) + return {} + } + + throw new Error('未知表: ' + table) +} + +async function applyDelete(op, ctx) { + const { assertMember } = ctx + const { table, uuid } = op + if (!uuid) throw new Error('缺少 uuid') + + if (table === 'Project') { + const row = await prisma.project.findUnique({ where: { uuid }, select: { id: true, isPersonal: true } }) + if (!row) return { skipped: true } + if (!(await assertMember(row.id))) throw new Error('无项目权限') + if (row.isPersonal) throw new Error('个人默认项目不可删除') + await prisma.project.delete({ where: { uuid } }) + return {} + } + if (table === 'BoardColumn') { + const row = await prisma.boardColumn.findUnique({ where: { uuid } }) + if (!row) return { skipped: true } + if (!(await assertMember(row.projectId))) throw new Error('无项目权限') + await prisma.boardColumn.delete({ where: { uuid } }) + return {} + } + if (table === 'ProjectMember') { + const row = await prisma.projectMember.findUnique({ where: { uuid } }) + if (!row) return { skipped: true } + if (!(await assertMember(row.projectId))) throw new Error('无项目权限') + await prisma.projectMember.delete({ where: { uuid } }) + return {} + } + if (table === 'Task') { + const row = await prisma.task.findUnique({ where: { uuid } }) + if (!row) return { skipped: true } + if (!(await assertMember(row.projectId))) throw new Error('无项目权限') + await prisma.task.delete({ where: { uuid } }) + return {} + } + if (table === 'TaskMember') { + const row = await prisma.taskMember.findUnique({ where: { uuid } }) + if (!row) return { skipped: true } + const task = await prisma.task.findUnique({ where: { id: row.taskId }, select: { projectId: true } }) + if (!task || !(await assertMember(task.projectId))) throw new Error('无项目权限') + await prisma.taskMember.delete({ where: { uuid } }) + return {} + } + if (table === 'TaskTag') { + const row = await prisma.taskTag.findUnique({ where: { uuid } }) + if (!row) return { skipped: true } + const task = await prisma.task.findUnique({ where: { id: row.taskId }, select: { projectId: true } }) + if (!task || !(await assertMember(task.projectId))) throw new Error('无项目权限') + await prisma.taskTag.delete({ where: { uuid } }) + return {} + } + throw new Error('未知表: ' + table) +} + +router.post('/upload', authRequired, async (req, res, next) => { + try { + const userId = req.user.sub + const ops = Array.isArray(req.body?.ops) ? req.body.ops : [] + if (!ops.length) return res.status(400).json({ error: 'ops 不能为空' }) + if (ops.length > 500) return res.status(400).json({ error: '单批 ops 过多 (上限 500)' }) + + const memberCache = new Map() + const assertMember = async (projectId) => { + if (!projectId) return false + if (memberCache.has(projectId)) return memberCache.get(projectId) + const ok = !!(await prisma.projectMember.findFirst({ where: { projectId, userId } })) + memberCache.set(projectId, ok) + return ok + } + const ctx = { userId, assertMember } + + // 依赖顺序: 父表先落地 (Project → BoardColumn → ProjectMember → Task → TaskMember/TaskTag); 删除反向 + const rank = (o) => TABLE_RANK[o?.table] ?? 99 + const puts = ops.filter((o) => o && o.op !== 'DELETE').sort((a, b) => rank(a) - rank(b)) + const dels = ops.filter((o) => o && o.op === 'DELETE').sort((a, b) => rank(b) - rank(a)) + + const results = [] + for (const op of puts) { + try { + const extra = await applyPut(op, ctx) + results.push({ id: op.id, ok: true, ...extra }) + } catch (e) { + results.push({ id: op.id, ok: false, error: e.message }) + } + } + for (const op of dels) { + try { + const extra = await applyDelete(op, ctx) + results.push({ id: op.id, ok: true, ...extra }) + } catch (e) { + results.push({ id: op.id, ok: false, error: e.message }) + } + } + res.json({ results }) + } catch (e) { next(e) } +}) + +export default router diff --git a/server/routes/projects.js b/server/routes/projects.js new file mode 100644 index 0000000..1ebf194 --- /dev/null +++ b/server/routes/projects.js @@ -0,0 +1,417 @@ +// 任务模块: 项目 / 成员 / 看板列 +import { Router } from 'express' +import { prisma } from '../db.js' +import { authRequired } from '../middleware.js' +import { loadProjectForUser, loadColumns, loadMemberIds } from '../task-domain/access.js' +import { genUuid, withUuid, normProjectCode, allocProjectCode } from '../task-domain/identity.js' + +const router = Router() +router.use(authRequired) + +// 新建项目时的默认 4 列 (T-COL-01, T-BD-09) +export const DEFAULT_COLUMNS = [ + { name: 'Plan', sortOrder: 0, boundStatus: 'todo' }, + { name: 'Doing', sortOrder: 1, boundStatus: 'doing' }, + { name: 'Action', sortOrder: 2, boundStatus: null }, + { name: 'Done', sortOrder: 3, boundStatus: 'done' }, +] + +export const MAX_COLUMNS = 10 +const MAX_NAME = 20 + +/** 建 4 个默认列 */ +async function createDefaultColumns(projectId) { + await prisma.boardColumn.createMany({ + data: withUuid(DEFAULT_COLUMNS.map((c) => ({ ...c, projectId, retention: 'forever' }))), + }) +} + +/** 确保用户有一个个人默认项目 (T-PJ-01), 幂等 */ +export async function ensurePersonalProject(userId) { + const exist = await prisma.project.findFirst({ where: { ownerId: userId, isPersonal: true } }) + if (exist) return exist + const user = await prisma.user.findUnique({ where: { id: userId }, select: { name: true } }) + const name = `${user?.name || '我'}的任务` + const project = await prisma.project.create({ + data: { + name, + ownerId: userId, + isPersonal: true, + uuid: genUuid(), + code: await allocProjectCode(name), + members: { create: { uuid: genUuid(), userId } }, + }, + }) + await createDefaultColumns(project.id) + return project +} + +// 任务类型标签: 项目级自定义, 未配置时用默认四类 +export const DEFAULT_TASK_KINDS = ['需求', 'Bug', 'FEATURE', '优化'] + +// 自动归档档位 (T-ARC-02) +export const AUTO_ARCHIVE_OPTIONS = ['3d', '1w', '1m', 'never'] + +export function normTaskKinds(v) { + if (!Array.isArray(v)) return null + const out = [] + for (const k of v) { + const s = String(k || '').trim().slice(0, 20) + if (s && !out.includes(s)) out.push(s) + if (out.length >= 30) break + } + return out +} + +function projectSummary(p) { + return { + id: p.id, + uuid: p.uuid, + code: p.code, + name: p.name, + ownerId: p.ownerId, + isPersonal: p.isPersonal, + archived: p.archived, + createdAt: p.createdAt, + updatedAt: p.updatedAt, + taskKinds: normTaskKinds(p.taskKinds) || DEFAULT_TASK_KINDS, + icon: p.icon || null, + autoArchive: AUTO_ARCHIVE_OPTIONS.includes(p.autoArchive) ? p.autoArchive : 'never', + memberCount: p._count?.members ?? undefined, + taskCount: p._count?.tasks ?? undefined, + } +} + +// ---------- 项目 ---------- + +// GET /api/projects 我参与的项目 +router.get('/', async (req, res, next) => { + try { + const userId = req.user.sub + await ensurePersonalProject(userId) + const rows = await prisma.project.findMany({ + where: { members: { some: { userId } } }, + include: { _count: { select: { members: true, tasks: { where: { archived: false } } } } }, + orderBy: [{ archived: 'asc' }, { updatedAt: 'desc' }], + }) + res.json({ items: rows.map(projectSummary) }) + } catch (e) { next(e) } +}) + +// POST /api/projects { name } +router.post('/', async (req, res, next) => { + try { + const userId = req.user.sub + const name = String(req.body?.name || '').trim() + if (!name) return res.status(400).json({ error: '项目名称不能为空' }) + if (name.length > MAX_NAME) return res.status(400).json({ error: `项目名称不能超过 ${MAX_NAME} 字` }) + + // 项目短编号: 可由客户端自拟 (2~8 位字母数字, 全局唯一), 未提供时按名称自动分配 + let code + const rawCode = req.body?.code + if (rawCode !== undefined && rawCode !== null && String(rawCode).trim() !== '') { + code = normProjectCode(rawCode) + if (!code) return res.status(400).json({ error: '项目编号须为 2~8 位字母数字' }) + const dup = await prisma.project.findUnique({ where: { code } }) + if (dup) return res.status(409).json({ error: `项目编号 ${code} 已被占用` }) + } else { + code = await allocProjectCode(name) + } + + const project = await prisma.project.create({ + data: { + name, + ownerId: userId, + isPersonal: false, + uuid: genUuid(), + code, + members: { create: { uuid: genUuid(), userId } }, + }, + }) + await createDefaultColumns(project.id) + const full = await prisma.project.findUnique({ + where: { id: project.id }, + include: { _count: { select: { members: true, tasks: { where: { archived: false } } } } }, + }) + res.json({ item: projectSummary(full) }) + } catch (e) { next(e) } +}) + +// GET /api/projects/:id 详情(项目 + 成员 + 列) +router.get('/:id', async (req, res, next) => { + try { + const userId = req.user.sub + const project = await loadProjectForUser(req.params.id, userId) + if (!project) return res.status(404).json({ error: '项目不存在或无权限' }) + const [members, columns] = await Promise.all([ + prisma.projectMember.findMany({ + where: { projectId: project.id }, + include: { user: { select: { id: true, name: true, email: true } } }, + orderBy: { createdAt: 'asc' }, + }), + loadColumns(project.id), + ]) + res.json({ + item: { + ...projectSummary(project), + members: members.map((m) => ({ userId: m.userId, name: m.user.name, email: m.user.email, permission: m.permission })), + columns, + notes: Array.isArray(project.notes) ? project.notes : [], + }, + }) + } catch (e) { next(e) } +}) + +// PUT /api/projects/:id { name?, archived?, notes? } +router.put('/:id', async (req, res, next) => { + try { + const userId = req.user.sub + const project = await loadProjectForUser(req.params.id, userId) + if (!project) return res.status(404).json({ error: '项目不存在或无权限' }) + const { name, archived, notes, taskKinds, icon, autoArchive, code } = req.body || {} + const data = {} + if (name !== undefined) { + const t = String(name).trim() + if (!t) return res.status(400).json({ error: '项目名称不能为空' }) + if (t.length > MAX_NAME) return res.status(400).json({ error: `项目名称不能超过 ${MAX_NAME} 字` }) + data.name = t + } + // 项目短编号: 可修改 (2~8 位字母数字, 全局唯一) + if (code !== undefined) { + const c = normProjectCode(code) + if (!c) return res.status(400).json({ error: '项目编号须为 2~8 位字母数字' }) + if (c !== project.code) { + const dup = await prisma.project.findUnique({ where: { code: c } }) + if (dup) return res.status(409).json({ error: `项目编号 ${c} 已被占用` }) + data.code = c + } + } + // 任务类型标签: 整体替换; 空数组回落默认 + if (taskKinds !== undefined) { + if (!Array.isArray(taskKinds)) return res.status(400).json({ error: '任务类型格式非法' }) + const kinds = normTaskKinds(taskKinds) + if (!kinds?.length) return res.status(400).json({ error: '至少保留一个任务类型' }) + data.taskKinds = kinds + } + // 项目图标: data URL (客户端已裁剪), null 清除回退首字色块 + if (icon !== undefined && icon !== null) { + const s = String(icon) + if (!s.startsWith('data:image/')) return res.status(400).json({ error: '图标格式非法' }) + if (s.length > 300_000) return res.status(400).json({ error: '图标过大' }) + data.icon = s + } else if (icon === null) { + data.icon = null + } + // 自动归档档位 (T-ARC-02) + if (autoArchive !== undefined) { + if (!AUTO_ARCHIVE_OPTIONS.includes(autoArchive)) return res.status(400).json({ error: '自动归档档位非法' }) + data.autoArchive = autoArchive + } + if (archived !== undefined) data.archived = !!archived + // 项目信息块 (T-PJ-13): 任意多个 { title, content }, 整体替换 + if (notes !== undefined) { + if (!Array.isArray(notes)) return res.status(400).json({ error: '信息块格式非法' }) + data.notes = notes.slice(0, 50).map((n) => ({ + title: String(n?.title ?? '').slice(0, 50), + content: String(n?.content ?? '').slice(0, 20000), + })) + } + const row = await prisma.project.update({ where: { id: project.id }, data }) + res.json({ item: projectSummary(row) }) + } catch (e) { next(e) } +}) + +// DELETE /api/projects/:id 个人项目拒绝 (T-PJ-02, T-EX-09) +router.delete('/:id', async (req, res, next) => { + try { + const userId = req.user.sub + const project = await loadProjectForUser(req.params.id, userId) + if (!project) return res.status(404).json({ error: '项目不存在或无权限' }) + if (project.isPersonal) return res.status(400).json({ error: '个人默认项目不可删除' }) + await prisma.project.delete({ where: { id: project.id } }) + res.json({ ok: true }) + } catch (e) { next(e) } +}) + +// ---------- 成员 ---------- + +// POST /api/projects/:id/members { userId } | { email } +router.post('/:id/members', async (req, res, next) => { + try { + const userId = req.user.sub + const project = await loadProjectForUser(req.params.id, userId) + if (!project) return res.status(404).json({ error: '项目不存在或无权限' }) + if (project.isPersonal) return res.status(400).json({ error: '个人默认项目不可添加成员' }) + + const { userId: targetId, email } = req.body || {} + let target = null + if (targetId) target = await prisma.user.findUnique({ where: { id: Number(targetId) } }) + else if (email) target = await prisma.user.findFirst({ where: { email: String(email).trim() } }) + if (!target) return res.status(404).json({ error: '用户不存在' }) + + const exist = await prisma.projectMember.findUnique({ + where: { projectId_userId: { projectId: project.id, userId: target.id } }, + }) + if (exist) return res.status(409).json({ error: '该用户已是项目成员' }) + + await prisma.projectMember.create({ data: { uuid: genUuid(), projectId: project.id, userId: target.id } }) + res.json({ ok: true, member: { userId: target.id, name: target.name } }) + } catch (e) { next(e) } +}) + +// DELETE /api/projects/:id/members/:uid 移除成员 / 自己退出 (T-PJ-06/07) +router.delete('/:id/members/:uid', async (req, res, next) => { + try { + const userId = req.user.sub + const project = await loadProjectForUser(req.params.id, userId) + if (!project) return res.status(404).json({ error: '项目不存在或无权限' }) + if (project.isPersonal) return res.status(400).json({ error: '个人默认项目不可移除成员' }) + + const target = Number(req.params.uid) + // 只能移除自己, 或是创建人移除他人 (本期权限均等, 允许成员自退) + if (target !== userId && project.ownerId !== userId) { + return res.status(403).json({ error: '只能移除自己,或由创建人移除他人' }) + } + await prisma.projectMember.deleteMany({ where: { projectId: project.id, userId: target } }) + // 成员清空后项目静默保留 (T-PJ-08) + res.json({ ok: true }) + } catch (e) { next(e) } +}) + +// ---------- 看板列 ---------- + +// GET /api/projects/:id/columns +router.get('/:id/columns', async (req, res, next) => { + try { + const userId = req.user.sub + const project = await loadProjectForUser(req.params.id, userId) + if (!project) return res.status(404).json({ error: '项目不存在或无权限' }) + res.json({ items: await loadColumns(project.id) }) + } catch (e) { next(e) } +}) + +// POST /api/projects/:id/columns { name } +router.post('/:id/columns', async (req, res, next) => { + try { + const userId = req.user.sub + const project = await loadProjectForUser(req.params.id, userId) + if (!project) return res.status(404).json({ error: '项目不存在或无权限' }) + + const name = String(req.body?.name || '').trim() + if (!name) return res.status(400).json({ error: '列名不能为空' }) + if (name.length > MAX_NAME) return res.status(400).json({ error: `列名不能超过 ${MAX_NAME} 字` }) + + const cols = await loadColumns(project.id) + if (cols.length >= MAX_COLUMNS) return res.status(400).json({ error: `最多 ${MAX_COLUMNS} 列` }) + if (cols.some((c) => c.name === name)) return res.status(409).json({ error: '列名已存在' }) + + const row = await prisma.boardColumn.create({ + data: { + projectId: project.id, + name, + sortOrder: cols.length ? Math.max(...cols.map((c) => c.sortOrder)) + 1 : 0, + boundStatus: null, + retention: 'forever', + }, + }) + res.json({ item: row }) + } catch (e) { next(e) } +}) + +// PUT /api/projects/:id/columns/order { ids: number[] } +router.put('/:id/columns/order', async (req, res, next) => { + try { + const userId = req.user.sub + const project = await loadProjectForUser(req.params.id, userId) + if (!project) return res.status(404).json({ error: '项目不存在或无权限' }) + const ids = Array.isArray(req.body?.ids) ? req.body.ids.map(Number) : [] + const cols = await loadColumns(project.id) + if (ids.length !== cols.length) return res.status(400).json({ error: '列数量不匹配' }) + + await prisma.$transaction( + ids.map((id, idx) => prisma.boardColumn.update({ where: { id }, data: { sortOrder: idx } })), + ) + res.json({ items: await loadColumns(project.id) }) + } catch (e) { next(e) } +}) + +// PUT /api/columns/:id { name?, boundStatus?, retention? } +export const columnRouter = Router() +columnRouter.use(authRequired) + +columnRouter.put('/:id', async (req, res, next) => { + try { + const userId = req.user.sub + const id = Number(req.params.id) + const col = await prisma.boardColumn.findUnique({ where: { id } }) + if (!col) return res.status(404).json({ error: '列不存在' }) + const project = await loadProjectForUser(col.projectId, userId) + if (!project) return res.status(404).json({ error: '项目不存在或无权限' }) + + const { name, boundStatus, retention } = req.body || {} + const data = {} + if (name !== undefined) { + const t = String(name).trim() + if (!t) return res.status(400).json({ error: '列名不能为空' }) + if (t.length > MAX_NAME) return res.status(400).json({ error: `列名不能超过 ${MAX_NAME} 字` }) + const dup = await prisma.boardColumn.findFirst({ where: { projectId: col.projectId, name: t, NOT: { id } } }) + if (dup) return res.status(409).json({ error: '列名已存在' }) + data.name = t + } + if (boundStatus !== undefined) { + // null | todo | doing | done + if (boundStatus !== null && !['todo', 'doing', 'done'].includes(boundStatus)) { + return res.status(400).json({ error: '绑定状态取值非法' }) + } + data.boundStatus = boundStatus + } + if (retention !== undefined) { + if (!['never', 'today', '3d', 'week', 'month', 'forever'].includes(retention)) { + return res.status(400).json({ error: '保留选项取值非法' }) + } + data.retention = retention + } + const row = await prisma.boardColumn.update({ where: { id }, data }) + res.json({ item: row }) + } catch (e) { next(e) } +}) + +// DELETE /api/columns/:id?moveToColumnId=N 删列并迁移任务 (T-COL-04, T-EX-16/17) +columnRouter.delete('/:id', async (req, res, next) => { + try { + const userId = req.user.sub + const id = Number(req.params.id) + const col = await prisma.boardColumn.findUnique({ where: { id } }) + if (!col) return res.status(404).json({ error: '列不存在' }) + const project = await loadProjectForUser(col.projectId, userId) + if (!project) return res.status(404).json({ error: '项目不存在或无权限' }) + + const cols = await loadColumns(col.projectId) + if (cols.length <= 1) return res.status(400).json({ error: '至少保留一列' }) + + // 迁移目标: 指定列 否则相邻列(优先后一个, 没有则前一个) + let targetId = Number(req.query.moveToColumnId) || 0 + if (!targetId) { + const idx = cols.findIndex((c) => c.id === id) + targetId = (cols[idx + 1] || cols[idx - 1]).id + } + const target = cols.find((c) => c.id === targetId) + if (!target || target.id === id) return res.status(400).json({ error: '迁移目标列非法' }) + + // 迁移任务: 状态按目标列绑定重新裁决 (设计文档 §2.4) + const tasks = await prisma.task.findMany({ where: { columnId: id } }) + const newStatus = target.boundStatus || null + await prisma.task.updateMany({ + where: { columnId: id }, + data: { + columnId: target.id, + ...(newStatus ? { status: newStatus } : {}), + }, + }) + await prisma.boardColumn.delete({ where: { id } }) + res.json({ ok: true, moved: tasks.length, toColumnId: target.id }) + } catch (e) { next(e) } +}) + +export { createDefaultColumns, loadMemberIds } +export default router diff --git a/server/routes/tasks.js b/server/routes/tasks.js new file mode 100644 index 0000000..019b15a --- /dev/null +++ b/server/routes/tasks.js @@ -0,0 +1,413 @@ +// 任务模块: 任务 / 子任务 / 跨项目拷贝 +import { Router } from 'express' +import { prisma } from '../db.js' +import { authRequired } from '../middleware.js' +import { + loadTaskForUser, loadProjectForUser, loadColumns, loadMemberIds, +} from '../task-domain/access.js' +import { resolveTaskWrite } from '../task-domain/resolveStatus.js' +import { filterVisible } from '../task-domain/retention.js' +import { copyTask } from '../task-domain/copyTask.js' +import { runAutoArchiveForProject } from '../task-domain/archive.js' +import { genUuid, withUuid, withTaskNumber } from '../task-domain/identity.js' + +const router = Router() +router.use(authRequired) + +const DATE_RE = /^\d{4}-\d{2}-\d{2}$/ + +const TASK_INCLUDE = { + column: true, + assignee: { select: { id: true, name: true } }, + creator: { select: { id: true, name: true } }, + members: { include: { user: { select: { id: true, name: true } } } }, + tags: true, + children: { + where: { archived: false }, + include: { + column: true, + assignee: { select: { id: true, name: true } }, + members: { include: { user: { select: { id: true, name: true } } } }, + tags: true, + }, + orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }], + }, +} + +// 归一化: members / tags 平铺为数组, 便于前端直接用 +function shape(task) { + if (!task) return task + const out = { + ...task, + members: (task.members || []).map((m) => ({ userId: m.userId, name: m.user?.name })), + tags: (task.tags || []).map((t) => t.name), + } + if (Array.isArray(task.children)) out.children = task.children.map(shape) + return out +} + +// ---------- 项目下的任务列表 ---------- + +// GET /api/projects/:id/tasks +router.get('/projects/:id/tasks', async (req, res, next) => { + try { + const userId = req.user.sub + const project = await loadProjectForUser(req.params.id, userId) + if (!project) return res.status(404).json({ error: '项目不存在或无权限' }) + + // 读时惰性自动归档 (T-ARC-02): 保证刚到期的隐藏任务当场消失 + await runAutoArchiveForProject(project.id) + + const columns = await loadColumns(project.id) + const rows = await prisma.task.findMany({ + where: { projectId: project.id, parentId: null, archived: false }, + include: TASK_INCLUDE, + orderBy: [{ sortOrder: 'asc' }, { createdAt: 'asc' }], + }) + const shaped = rows.map(shape) + // 保留选项过滤 (T-FL-*): 已完成任务按所属列配置隐藏 + const visible = filterVisible(shaped, columns) + res.json({ items: visible, columns, total: shaped.length }) + } catch (e) { next(e) } +}) + +// ---------- 归档 (T-ARC) ---------- + +// POST /api/tasks/:id/archive 手动归档: 连带全部子任务, 幂等 +router.post('/tasks/:id/archive', async (req, res, next) => { + try { + const userId = req.user.sub + const found = await loadTaskForUser(req.params.id, userId) + if (!found) return res.status(404).json({ error: '任务不存在或无权限' }) + const { task } = found + const children = await prisma.task.findMany({ where: { parentId: task.id }, select: { id: true } }) + const r = await prisma.task.updateMany({ + where: { id: { in: [task.id, ...children.map((c) => c.id)] } }, + data: { archived: true, archivedAt: new Date(), archiveSource: 'manual' }, + }) + res.json({ ok: true, count: r.count }) + } catch (e) { next(e) } +}) + +// POST /api/tasks/:id/unarchive 恢复: 回原列, 状态不变, 连带子任务 +router.post('/tasks/:id/unarchive', async (req, res, next) => { + try { + const userId = req.user.sub + const found = await loadTaskForUser(req.params.id, userId) + if (!found) return res.status(404).json({ error: '任务不存在或无权限' }) + const { task } = found + const children = await prisma.task.findMany({ where: { parentId: task.id }, select: { id: true } }) + const r = await prisma.task.updateMany({ + where: { id: { in: [task.id, ...children.map((c) => c.id)] } }, + data: { archived: false, archivedAt: null, archiveSource: null }, + }) + res.json({ ok: true, count: r.count }) + } catch (e) { next(e) } +}) + +// GET /api/projects/:id/tasks/archived 已归档查询: 过滤 + 游标分页 (T-ARC-04) +router.get('/projects/:id/tasks/archived', async (req, res, next) => { + try { + const userId = req.user.sub + const project = await loadProjectForUser(req.params.id, userId) + if (!project) return res.status(404).json({ error: '项目不存在或无权限' }) + + const q = req.query || {} + const where = { projectId: project.id, archived: true, parentId: null } + if (q.keyword) where.title = { contains: String(q.keyword).trim() } + if (q.kind) where.kind = String(q.kind) + if (q.columnId && Number.isFinite(Number(q.columnId))) where.columnId = Number(q.columnId) + if (q.from && DATE_RE.test(q.from)) where.archivedAt = { ...(where.archivedAt || {}), gte: new Date(q.from + 'T00:00:00+08:00') } + if (q.to && DATE_RE.test(q.to)) where.archivedAt = { ...(where.archivedAt || {}), lte: new Date(q.to + 'T23:59:59.999+08:00') } + + const limit = Math.min(100, Math.max(1, Number(q.limit) || 20)) + if (q.cursor) { + const [ts, id] = String(q.cursor).split('|') + const t = new Date(ts) + if (Number.isNaN(t.getTime()) || !Number.isFinite(Number(id))) { + return res.status(400).json({ error: 'cursor 无效' }) + } + where.OR = [ + { archivedAt: { lt: t } }, + { archivedAt: t, id: { lt: Number(id) } }, + ] + } + + const rows = await prisma.task.findMany({ + where, + include: TASK_INCLUDE, + orderBy: [{ archivedAt: 'desc' }, { id: 'desc' }], + take: limit + 1, + }) + const hasMore = rows.length > limit + const page = rows.slice(0, limit) + const last = page[page.length - 1] + const nextCursor = hasMore && last ? `${last.archivedAt.toISOString()}|${last.id}` : null + res.json({ items: page.map(shape), nextCursor }) + } catch (e) { next(e) } +}) + +// POST /api/projects/:id/tasks { title, description?, parentId?, assigneeId?, memberIds?, tags? } +router.post('/projects/:id/tasks', async (req, res, next) => { + try { + const userId = req.user.sub + const project = await loadProjectForUser(req.params.id, userId) + if (!project) return res.status(404).json({ error: '项目不存在或无权限' }) + + const { title, description = '', parentId, assigneeId, memberIds = [], tags = [], kind, markDate } = req.body || {} + const cleanTitle = String(title || '').trim() + if (!cleanTitle) return res.status(400).json({ error: '任务标题不能为空' }) + if (cleanTitle.length > 30) return res.status(400).json({ error: '任务标题不能超过 30 字' }) + const mark = parseMarkDate(markDate) + + const columns = await loadColumns(project.id) + if (!columns.length) return res.status(400).json({ error: '项目没有可用的看板列' }) + + // 子任务校验: 父任务必须存在、同项目、且本身是顶层任务 (T-ST-01/02, T-EX-12) + let pid = null + if (parentId) { + const parent = await prisma.task.findUnique({ where: { id: Number(parentId) } }) + if (!parent) return res.status(404).json({ error: '父任务不存在' }) + if (parent.projectId !== project.id) return res.status(400).json({ error: '子任务必须与父任务同项目' }) + if (parent.parentId) return res.status(400).json({ error: '仅支持一级子任务' }) + pid = parent.id + } + + const memberIds_ = await loadMemberIds(project.id) + const memberSet = new Set(memberIds_) + // 负责人: 未指定 => 默认创建者本人 (T-TK-09); 显式 null => 不指派 + let finalAssignee = null + if (assigneeId === undefined) finalAssignee = memberSet.has(userId) ? userId : null + else if (assigneeId !== null) { + const aid = Number(assigneeId) + if (!memberSet.has(aid)) return res.status(400).json({ error: '负责人必须是项目成员' }) + finalAssignee = aid + } + + const firstCol = columns[0] + const taskData = { + uuid: req.body?.uuid || genUuid(), + projectId: project.id, + columnId: firstCol.id, + parentId: pid, + title: cleanTitle, + description: String(description || ''), + kind: kind ? String(kind).trim().slice(0, 20) : null, + markDate: mark, + status: firstCol.boundStatus || 'todo', + assigneeId: finalAssignee, + creatorId: userId, + extras: [], + members: { + create: withUuid((Array.isArray(memberIds) ? memberIds : []) + .map(Number).filter((id) => memberSet.has(id)) + .map((uid) => ({ userId: uid }))), + }, + tags: { create: withUuid((Array.isArray(tags) ? tags : []).filter(Boolean).map((n) => ({ name: String(n).slice(0, 20) }))) }, + } + // 项目内自增编号 (展示 `code-number`): 服务端分配, 并发冲突时重取 max+1 重试 + const { value: row } = await withTaskNumber(project.id, (number) => + prisma.task.create({ data: { ...taskData, number }, include: TASK_INCLUDE }), + ) + res.json({ item: shape(row) }) + } catch (e) { next(e) } +}) + +// POST /api/projects/:id/tasks/reorder { ids: [顶层任务 id 的新顺序] } +// 按数组下标重写 sortOrder (组内拖动排序; 子任务不参与, 跟随父任务) +router.post('/projects/:id/tasks/reorder', async (req, res, next) => { + try { + const userId = req.user.sub + const project = await loadProjectForUser(req.params.id, userId) + if (!project) return res.status(404).json({ error: '项目不存在或无权限' }) + const ids = Array.isArray((req.body || {}).ids) ? req.body.ids.map(Number).filter(Number.isFinite) : [] + if (!ids.length) return res.status(400).json({ error: 'ids 不能为空' }) + const rows = await prisma.task.findMany({ + where: { projectId: project.id, id: { in: ids }, parentId: null }, + select: { id: true }, + }) + if (rows.length !== new Set(ids).size) return res.status(400).json({ error: '包含无效的任务' }) + await prisma.$transaction( + ids.map((id, i) => prisma.task.update({ where: { id }, data: { sortOrder: i } })), + ) + res.json({ ok: true }) + } catch (e) { next(e) } +}) + +// ---------- 单条任务 ---------- + +// GET /api/tasks/:id +router.get('/tasks/:id', async (req, res, next) => { + try { + const userId = req.user.sub + const found = await loadTaskForUser(req.params.id, userId) + if (!found) return res.status(404).json({ error: '任务不存在或无权限' }) + res.json({ item: shape(found.task) }) + } catch (e) { next(e) } +}) + +// 时间标记: 接受 YYYY-MM-DD (或完整 ISO), 存 DateTime; 非法输入 400 由调用方处理 +function parseMarkDate(v) { + if (v === null || v === undefined || v === '') return null + const d = new Date(v) + if (Number.isNaN(d.getTime())) return undefined // 非法 + return d +} + +// PUT /api/tasks/:id 关键: 列/状态联动裁决 +router.put('/tasks/:id', async (req, res, next) => { + try { + const userId = req.user.sub + const found = await loadTaskForUser(req.params.id, userId) + if (!found) return res.status(404).json({ error: '任务不存在或无权限' }) + const { task, project } = found + const body = req.body || {} + + const columns = await loadColumns(project.id) + const data = {} + + // 文本 / 扩展信息 + if (body.title !== undefined) { + const t = String(body.title).trim() + if (!t) return res.status(400).json({ error: '任务标题不能为空' }) + if (t.length > 30) return res.status(400).json({ error: '任务标题不能超过 30 字' }) + data.title = t + } + if (body.description !== undefined) data.description = String(body.description) + // 任务类型标签: null 清空, 否则截断保存 + if (body.kind !== undefined) { + const k = String(body.kind || '').trim().slice(0, 20) + data.kind = k || null + } + // 时间标记: 完整日期存储 + if (body.markDate !== undefined) { + const mark = parseMarkDate(body.markDate) + if (mark === undefined) return res.status(400).json({ error: '时间标记格式非法' }) + data.markDate = mark + } + if (body.extras !== undefined) { + if (!Array.isArray(body.extras)) return res.status(400).json({ error: '扩展信息格式非法' }) + data.extras = body.extras.map((e) => ({ + label: String(e?.label ?? '').slice(0, 40), + value: String(e?.value ?? ''), + url: e?.url ? String(e.url) : '', + })) + } + if (body.sortOrder !== undefined) data.sortOrder = Number(body.sortOrder) || 0 + + // 负责人: 必须是项目成员或 null (T-AS-03) + const memberIds = await loadMemberIds(project.id) + const memberSet = new Set(memberIds) + if (body.assigneeId !== undefined) { + if (body.assigneeId === null || body.assigneeId === '') data.assigneeId = null + else { + const aid = Number(body.assigneeId) + if (!memberSet.has(aid)) return res.status(400).json({ error: '负责人必须是项目成员' }) + data.assigneeId = aid + } + } + + // 列 / 状态: 走统一裁决 (T-BD-*) + const patch = {} + if (body.columnId !== undefined) patch.columnId = Number(body.columnId) + if (body.status !== undefined) { + if (!['todo', 'doing', 'done'].includes(body.status)) return res.status(400).json({ error: '任务状态取值非法' }) + patch.status = body.status + } + if (patch.columnId !== undefined && !columns.some((c) => c.id === patch.columnId)) { + return res.status(400).json({ error: '看板列不存在' }) + } + + let finalStatus = task.status + if (Object.keys(patch).length) { + const r = resolveTaskWrite(task, patch, columns) + data.status = r.status + data.columnId = r.columnId + finalStatus = r.status + } + + // 完成时间: 进入 done 写 now, 退出 done 置空 (T-FL-10) + if (finalStatus === 'done' && task.status !== 'done') data.doneAt = new Date() + else if (finalStatus !== 'done' && data.status !== undefined) data.doneAt = null + + // 成员 / 标签: 整体替换 + if (Array.isArray(body.memberIds)) { + const ids = body.memberIds.map(Number).filter((id) => memberSet.has(id)) + await prisma.taskMember.deleteMany({ where: { taskId: task.id } }) + if (ids.length) { + await prisma.taskMember.createMany({ + data: withUuid(ids.map((uid) => ({ taskId: task.id, userId: uid }))), + }) + } + } + if (Array.isArray(body.tags)) { + await prisma.taskTag.deleteMany({ where: { taskId: task.id } }) + const names = body.tags.map((n) => String(n).trim()).filter(Boolean).slice(0, 10) + if (names.length) { + await prisma.taskTag.createMany({ data: withUuid(names.map((name) => ({ taskId: task.id, name: name.slice(0, 20) }))) }) + } + } + + // 拖拽降为子任务 (T-ST-12~15) + if (body.parentId !== undefined) { + if (body.parentId === null) { + data.parentId = null + } else { + const pid = Number(body.parentId) + if (pid === task.id) return res.status(400).json({ error: '不能成为自己的子任务' }) + const parent = await prisma.task.findUnique({ where: { id: pid } }) + if (!parent) return res.status(404).json({ error: '目标任务不存在' }) + if (parent.projectId !== task.projectId) return res.status(400).json({ error: '子任务必须与父任务同项目' }) + if (parent.parentId) return res.status(400).json({ error: '仅支持一级子任务' }) + const childCount = await prisma.task.count({ where: { parentId: task.id } }) + if (childCount > 0) return res.status(400).json({ error: '该任务已有子任务,不能成为子任务' }) + data.parentId = pid + } + } + + const row = await prisma.task.update({ where: { id: task.id }, data, include: TASK_INCLUDE }) + res.json({ item: shape(row) }) + } catch (e) { next(e) } +}) + +// DELETE /api/tasks/:id 级联删除子任务 (T-ST-11, T-EX-07) +router.delete('/tasks/:id', async (req, res, next) => { + try { + const userId = req.user.sub + const found = await loadTaskForUser(req.params.id, userId) + if (!found) return res.status(404).json({ error: '任务不存在或无权限' }) + const childCount = await prisma.task.count({ where: { parentId: found.task.id } }) + await prisma.task.delete({ where: { id: found.task.id } }) + res.json({ ok: true, deletedSubtasks: childCount }) + } catch (e) { next(e) } +}) + +// POST /api/tasks/:id/copy { projectId } 跨项目拷贝 (T-CP-*) +router.post('/tasks/:id/copy', async (req, res, next) => { + try { + const userId = req.user.sub + const found = await loadTaskForUser(req.params.id, userId) + if (!found) return res.status(404).json({ error: '任务不存在或无权限' }) + + const targetId = Number(req.body?.projectId) + const targetProject = await loadProjectForUser(targetId, userId) + if (!targetProject) return res.status(403).json({ error: '目标项目不存在或你不是其成员' }) + + const targetColumns = await loadColumns(targetProject.id) + if (!targetColumns.length) return res.status(400).json({ error: '目标项目没有可用的看板列' }) + const targetMemberIds = await loadMemberIds(targetProject.id) + + // 源任务需带列名映射信息 + const sourceColumns = await loadColumns(found.project.id) + const source = { ...found.task, __columns: sourceColumns } + + const result = await copyTask({ + source, targetProject, targetColumns, operatorId: userId, targetMemberIds, + }) + + const fresh = await prisma.task.findUnique({ where: { id: result.task.id }, include: TASK_INCLUDE }) + res.json({ item: shape(fresh), dropped: result.dropped }) + } catch (e) { next(e) } +}) + +export default router diff --git a/server/task-domain/access.js b/server/task-domain/access.js new file mode 100644 index 0000000..c2cb7da --- /dev/null +++ b/server/task-domain/access.js @@ -0,0 +1,58 @@ +// 任务模块: 项目成员校验 (T-PM-01, B-13) +// 所有 /api/projects/:id/* 与任务写操作都必须确认请求者是项目成员. +import { prisma } from '../db.js' + +/** 取出当前用户可访问的项目 (不存在或非成员 -> null) */ +export async function loadProjectForUser(projectId, userId) { + const id = Number(projectId) + if (!Number.isInteger(id) || id <= 0) return null + const project = await prisma.project.findFirst({ + where: { id, members: { some: { userId } } }, + }) + return project || null +} + +/** 任务 -> 校验其项目可访问, 返回 { task, project } */ +export async function loadTaskForUser(taskId, userId) { + const id = Number(taskId) + if (!Number.isInteger(id) || id <= 0) return null + const task = await prisma.task.findFirst({ + where: { id, project: { members: { some: { userId } } } }, + include: { + column: true, + assignee: { select: { id: true, name: true } }, + creator: { select: { id: true, name: true } }, + members: { include: { user: { select: { id: true, name: true } } } }, + tags: true, + children: { + include: { + column: true, + assignee: { select: { id: true, name: true } }, + members: { include: { user: { select: { id: true, name: true } } } }, + tags: true, + }, + orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }], + }, + }, + }) + if (!task) return null + const project = await prisma.project.findUnique({ where: { id: task.projectId } }) + return { task, project } +} + +/** 项目全部列 (按显示顺序) */ +export async function loadColumns(projectId) { + return prisma.boardColumn.findMany({ + where: { projectId: Number(projectId) }, + orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }], + }) +} + +/** 项目成员 userId 集合 */ +export async function loadMemberIds(projectId) { + const rows = await prisma.projectMember.findMany({ + where: { projectId: Number(projectId) }, + select: { userId: true }, + }) + return rows.map((r) => r.userId) +} diff --git a/server/task-domain/archive.js b/server/task-domain/archive.js new file mode 100644 index 0000000..aa84760 --- /dev/null +++ b/server/task-domain/archive.js @@ -0,0 +1,112 @@ +// 任务归档领域逻辑 (T-ARC) +// 自动归档判定: 倒计时起点 = 任务"被隐藏的时刻"(与 date.js isVisibleByRetention +// 精确同口径推导): 完成日 + 保留窗口后的上海零点; 加上项目 autoArchive 档位即到期时刻。 +// 执行双保险: 项目任务加载时惰性执行 (runAutoArchiveForProject) + 每小时 cron 兜底。 +import { prisma } from '../db.js' +import { toDateStr, addDays, startOfWeek, startOfMonth } from './date.js' + +export const AUTO_ARCHIVE_OPTIONS = ['3d', '1w', '1m', 'never'] +const DAY_MS = 24 * 3600 * 1000 +const SHANGHAI_OFFSET_MS = 8 * 3600 * 1000 + +/** 上海日历日 "YYYY-MM-DD" 的零点时刻 (UTC 表示) */ +function shanghaiMidnight(dayStr) { + const [y, m, d] = dayStr.split('-').map(Number) + return new Date(Date.UTC(y, m - 1, d) - SHANGHAI_OFFSET_MS) +} + +/** 上海日历日 + n 个月后的零点 (月末溢出由 Date 归一化) */ +function addMonths(dayStr, n) { + const [y, m, d] = dayStr.split('-').map(Number) + return new Date(Date.UTC(y, m - 1 + n, d) - SHANGHAI_OFFSET_MS) +} + +/** + * 任务被隐藏的时刻: doneAt 所在完成日之后, 保留窗口结束的上海零点。 + * 与 date.js isVisibleByRetention 的可见判定精确同口径: + * never → doneAt 即刻隐藏; today → 完成日次日零点; 3d → 完成日+3 零点; + * week → 完成周次下一周一零点; month → 次月 1 日零点; forever → 永不隐藏 (null) + */ +export function hiddenAtOf(doneAt, option) { + if (!doneAt) return null + const day = toDateStr(doneAt) + switch (option) { + case 'never': return new Date(doneAt) + case 'today': return shanghaiMidnight(addDays(day, 1)) + case '3d': return shanghaiMidnight(addDays(day, 3)) + case 'week': return shanghaiMidnight(addDays(startOfWeek(day), 7)) + case 'month': return shanghaiMidnight(String(addDays(day, 32)).slice(0, 8) + '01') + default: return null // forever: 永不隐藏 + } +} + +/** 自动归档到期时刻: 隐藏时刻 + 档位; 'never' 或无法隐藏 → null */ +export function autoArchiveDue(autoArchive, doneAt, columnRetention) { + if (!autoArchive || autoArchive === 'never') return null + const hidden = hiddenAtOf(doneAt, columnRetention) + if (!hidden) return null + switch (autoArchive) { + case '3d': return new Date(hidden.getTime() + 3 * DAY_MS) + case '1w': return new Date(hidden.getTime() + 7 * DAY_MS) + case '1m': { + const d = new Date(hidden) + d.setUTCMonth(d.getUTCMonth() + 1) + return d + } + default: return null + } +} + +/** + * 对单个项目执行一轮到期自动归档 (T-ARC-02)。 + * 只处理 status=done / doneAt 非空 / 未归档 的任务; 幂等。 + * @returns {number} 本次归档条数 + */ +export async function runAutoArchiveForProject(projectId) { + const project = await prisma.project.findUnique({ + where: { id: projectId }, + select: { autoArchive: true }, + }) + if (!project || !AUTO_ARCHIVE_OPTIONS.includes(project.autoArchive) || project.autoArchive === 'never') return 0 + + const columns = await prisma.boardColumn.findMany({ + where: { projectId }, + select: { id: true, retention: true }, + }) + const retentionById = new Map(columns.map((c) => [c.id, c.retention || 'forever'])) + + const tasks = await prisma.task.findMany({ + where: { projectId, archived: false, status: 'done', doneAt: { not: null } }, + select: { id: true, doneAt: true, columnId: true }, + }) + const now = new Date() + const dueIds = [] + for (const t of tasks) { + const due = autoArchiveDue(project.autoArchive, t.doneAt, retentionById.get(t.columnId)) + if (due && due <= now) dueIds.push(t.id) + } + if (!dueIds.length) return 0 + + const r = await prisma.task.updateMany({ + where: { id: { in: dueIds } }, + data: { archived: true, archivedAt: now, archiveSource: 'auto' }, + }) + return r.count +} + +/** 每小时 cron 兜底: 扫描所有启用了自动归档的项目 */ +export async function runAutoArchiveSweep() { + const projects = await prisma.project.findMany({ + where: { autoArchive: { not: 'never' } }, + select: { id: true }, + }) + let total = 0 + for (const p of projects) { + try { + total += await runAutoArchiveForProject(p.id) + } catch (e) { + console.error('[archive] 项目自动归档失败:', p.id, e.message) + } + } + return total +} diff --git a/server/task-domain/copyTask.js b/server/task-domain/copyTask.js new file mode 100644 index 0000000..fc8cf0b --- /dev/null +++ b/server/task-domain/copyTask.js @@ -0,0 +1,97 @@ +// 任务模块: 跨项目拷贝 (T-CP-*) +// 拷贝而非移动: 新建记录 + 深拷贝子任务, 源任务零改动. +import { prisma } from '../db.js' +import { genUuid, withUuid, withTaskNumber } from './identity.js' + +/** + * 拷贝一条任务(含子任务)到目标项目。 + * + * 规则: + * - 全字段复制: 标题/描述/状态/列/扩展卡片/标签 (T-CP-02) + * - 子任务一并复制, 且归属目标项目 (T-CP-03, T-CP-08) + * - creatorId = 操作者 (T-CP-04) + * - 负责人 / 任务成员只保留"属于目标项目成员"的部分, 其余丢弃并回报 (T-CP-07, T-EX-08) + * - 状态与列沿用源值, 不强行纠正 (T-EX-14) + * + * @returns {{ task, dropped: { assignee: null|{id,name}, members: {id,name}[] } }} + */ +export async function copyTask({ source, targetProject, targetColumns, operatorId, targetMemberIds }) { + const memberSet = new Set(targetMemberIds) + + // 列映射: 目标项目里找同名列, 找不到就用第一列 (保证 columnId 合法) + const pickColumn = (sourceColumnId) => { + const srcCol = source.__columns?.find((c) => c.id === sourceColumnId) + if (srcCol) { + const same = targetColumns.find((c) => c.name === srcCol.name) + if (same) return same.id + } + return targetColumns[0]?.id + } + + // 人员过滤 + const keepAssignee = source.assigneeId && memberSet.has(source.assigneeId) ? source.assigneeId : null + const droppedMembers = [] + const keptMemberIds = [] + for (const m of source.members || []) { + if (memberSet.has(m.userId)) keptMemberIds.push(m.userId) + else droppedMembers.push(m.user) + } + const droppedAssignee = source.assigneeId && !memberSet.has(source.assigneeId) ? source.assignee : null + + const baseData = (extra) => ({ + uuid: genUuid(), + projectId: targetProject.id, + title: source.title, + description: source.description || '', + status: source.status, + columnId: extra.columnId, + parentId: extra.parentId ?? null, + assigneeId: extra.parentId ? (source.assigneeId && memberSet.has(source.assigneeId) ? source.assigneeId : null) : keepAssignee, + creatorId: operatorId, + extras: source.extras ?? [], + doneAt: source.status === 'done' ? (source.doneAt || new Date()) : null, + sortOrder: source.sortOrder ?? 0, + tags: { + create: withUuid((source.tags || []).map((t) => ({ name: t.name }))), + }, + }) + + const { value: newTask } = await withTaskNumber(targetProject.id, (number) => + prisma.task.create({ + data: { + ...baseData({ columnId: pickColumn(source.columnId) }), + number, + members: { + create: withUuid(keptMemberIds.map((uid) => ({ userId: uid }))), + }, + }, + }), + ) + + // 子任务: 同法复制, parentId 指向新父任务 + for (const child of source.children || []) { + const childMembers = (child.members || []) + .filter((m) => memberSet.has(m.userId)) + .map((m) => m.userId) + await withTaskNumber(targetProject.id, (number) => + prisma.task.create({ + data: { + ...baseData({ + columnId: pickColumn(child.columnId), + parentId: newTask.id, + }), + number, + members: { create: withUuid(childMembers.map((uid) => ({ userId: uid }))) }, + }, + }), + ) + } + + return { + task: newTask, + dropped: { + assignee: droppedAssignee ? { id: droppedAssignee.id, name: droppedAssignee.name } : null, + members: droppedMembers.map((u) => ({ id: u.id, name: u.name })), + }, + } +} diff --git a/server/task-domain/date.js b/server/task-domain/date.js new file mode 100644 index 0000000..7c56be8 --- /dev/null +++ b/server/task-domain/date.js @@ -0,0 +1,93 @@ +// 任务模块: 自然日边界工具 (Asia/Shanghai) +// 与前端 src/pages/task/date.js 保持同一口径, 改动需同步 +// +// 保留选项口径 (T-FL-02): +// never 永不显示 +// today 当天 -> 今天 00:00 +// 3d 3 天 -> 今天-2 天 00:00 (含今天共 3 个自然日) +// week 本周 -> 本周一 00:00 +// month 本月 -> 本月 1 日 00:00 +// forever 永久显示 -> 无下界 + +export const TZ = 'Asia/Shanghai' + +export const RETENTION = ['never', 'today', '3d', 'week', 'month', 'forever'] +export const RETENTION_LABEL = { + never: '永不显示', + today: '当天', + '3d': '3 天', + week: '本周', + month: '本月', + forever: '永久显示', +} + +export const STATUSES = ['todo', 'doing', 'done'] +export const STATUS_LABEL = { todo: '未开始', doing: '执行中', done: '完成' } + +// 当前时刻在 Asia/Shanghai 的 "YYYY-MM-DD" 与星期几 +const FMT = new Intl.DateTimeFormat('en-CA', { + timeZone: TZ, year: 'numeric', month: '2-digit', day: '2-digit', +}) + +/** 任意时刻 -> 本地日历日 "YYYY-MM-DD" */ +export function toDateStr(d = new Date()) { + return FMT.format(d instanceof Date ? d : new Date(d)) +} + +/** "YYYY-MM-DD" -> UTC 零点 Date (仅做日期运算) */ +function toUTC(s) { + const [y, m, d] = String(s).split('-').map(Number) + return new Date(Date.UTC(y, (m || 1) - 1, d || 1)) +} + +function fromUTC(dt) { + return dt.toISOString().slice(0, 10) +} + +export function addDays(s, n) { + const d = toUTC(s) + d.setUTCDate(d.getUTCDate() + n) + return fromUTC(d) +} + +/** 周一为一周第一天 */ +export function startOfWeek(s) { + const dow = (toUTC(s).getUTCDay() + 6) % 7 + return addDays(s, -dow) +} + +export function startOfMonth(s) { + return String(s).slice(0, 8) + '01' +} + +/** + * 保留选项 -> 起始时间(不含时区偏移的 Date, 以 UTC 表示当日零点) + * 返回 null 表示"全可见"(forever); 返回 'never' 表示"全隐藏" + */ +export function retentionStart(option, now = new Date()) { + const today = toDateStr(now) + switch (option) { + case 'never': return { mode: 'never' } + case 'forever': return { mode: 'all' } + case 'today': return { mode: 'since', date: today } + case '3d': return { mode: 'since', date: addDays(today, -2) } + case 'week': return { mode: 'since', date: startOfWeek(today) } + case 'month': return { mode: 'since', date: startOfMonth(today) } + default: return { mode: 'all' } + } +} + +/** + * 判断一条已完成任务在给定保留选项下是否可见。 + * 依据任务的完成时刻 doneAt (T-FL-10); 未完成任务恒可见 (T-FL-12)。 + */ +export function isVisibleByRetention(task, option, now = new Date()) { + if (task.status !== 'done') return true + const win = retentionStart(option, now) + if (win.mode === 'never') return false + if (win.mode === 'all') return true + if (!task.doneAt) return false + // doneAt 是绝对时刻, 转成本地日历日再与窗口起点比较 + const doneDay = toDateStr(task.doneAt) + return doneDay >= win.date +} diff --git a/server/task-domain/identity.js b/server/task-domain/identity.js new file mode 100644 index 0000000..a0fdc66 --- /dev/null +++ b/server/task-domain/identity.js @@ -0,0 +1,74 @@ +// 任务域同步标识与编号 (0.2.1 离线编辑, 见 docs/V0.2/离线编辑模块/0.2.1实施方案.md) +// 约定: 任务域 6 表均带 uuid(客户端/服务端创建时生成, 上行按 uuid 幂等); +// Project.code 为用户自拟 2~8 位字母数字短编号(存储大写, 全局唯一); +// Task.number 为项目内自增编号, 服务端分配, 展示 `code-number`。 +import crypto from 'node:crypto' +import { prisma } from '../db.js' + +/** 同步标识: 服务端创建行时统一生成 uuid */ +export function genUuid() { + return crypto.randomUUID() +} + +/** 为多行数据补 uuid (createMany 前用) */ +export function withUuid(rows) { + return rows.map((r) => ({ ...r, uuid: r.uuid || genUuid() })) +} + +// 项目编号: 2~8 位字母数字, 存储统一大写 (任务展示 `编号-序号`, 不允许连字符) +const CODE_RE = /^[A-Z0-9]{2,8}$/ + +/** 归一化项目编号; 非法返回 null */ +export function normProjectCode(v) { + const s = String(v || '').trim().toUpperCase() + return CODE_RE.test(s) ? s : null +} + +function randCode(len) { + return crypto.randomBytes(len * 2).toString('hex').toUpperCase().replace(/[^A-Z0-9]/g, '').padEnd(len, 'X').slice(0, len) +} + +/** 从项目名推导候选编号: 取 ASCII 字母数字, 不足则随机补齐 */ +function suggestCode(name) { + const letters = String(name || '').toUpperCase().replace(/[^A-Z0-9]/g, '') + if (letters.length >= 2) return letters.slice(0, 8) + return (letters + randCode(8)).slice(0, 8) +} + +/** 分配一个未占用的项目编号: 优先按名称推导, 冲突则随机尾缀重试 */ +export async function allocProjectCode(name) { + const base = suggestCode(name) + for (let i = 0; i < 20; i++) { + const cand = i === 0 ? base : (base.slice(0, 5) + randCode(3)).slice(0, 8) + if (cand.length < 2 || !CODE_RE.test(cand)) continue + const dup = await prisma.project.findUnique({ where: { code: cand } }) + if (!dup) return cand + } + while (true) { + const cand = randCode(6) + const dup = await prisma.project.findUnique({ where: { code: cand } }) + if (!dup) return cand + } +} + +/** 任务项目内下一个编号 (max+1); 并发冲突由调用方捕获 P2002 重试 */ +export async function nextTaskNumber(projectId) { + const agg = await prisma.task.aggregate({ where: { projectId }, _max: { number: true } }) + return (agg._max.number ?? 0) + 1 +} + +/** Task.number 唯一冲突重试包装: fn(number) 返回 Promise; 冲突时重取编号, 最多 3 次 */ +export async function withTaskNumber(projectId, fn) { + let lastErr = null + for (let attempt = 0; attempt < 3; attempt++) { + const number = await nextTaskNumber(projectId) + try { + return { number, value: await fn(number) } + } catch (e) { + const target = Array.isArray(e?.meta?.target) ? e.meta.target.join(',') : String(e?.meta?.target || '') + lastErr = e + if (!(e?.code === 'P2002' && target.includes('number'))) throw e + } + } + throw lastErr +} diff --git a/server/task-domain/resolveStatus.js b/server/task-domain/resolveStatus.js new file mode 100644 index 0000000..bcc3993 --- /dev/null +++ b/server/task-domain/resolveStatus.js @@ -0,0 +1,85 @@ +// 任务模块: 看板列 与 任务状态 的联动裁决 (唯一口径) +// 对应需求: T-BD-04/05/06/07/08/13/14/15/16 +// +// 两个正交维度: +// Task.columnId 空间 (哪一列) +// Task.status 属性 (todo/doing/done) +// 列上 boundStatus 决定二者是否联动: +// null 该列与状态完全独立 +// todo等 该列绑定到该状态 +// +// 三种场景: +// A 拖拽换列 -> 只看目标列: 目标列绑定则状态跟随, 不绑定则保留原状态 +// B 改状态 -> 当前列已匹配则不改列; 否则按列序找第一个绑定该状态的列 (找不到就不动) +// C 子任务 -> 跳过全部联动 + +/** 列按 sortOrder 升序 (调用方保证已排序, 这里再兜底一次) */ +export function sortColumns(columns) { + return [...columns].sort((a, b) => (a.sortOrder - b.sortOrder) || (a.id - b.id)) +} + +/** + * 场景 A: 任务从任意列拖到 targetColumn + * @returns {{ status: string }} 新状态 + */ +export function statusOnColumnChange(currentStatus, targetColumn) { + if (!targetColumn) return { status: currentStatus } + if (targetColumn.boundStatus) return { status: targetColumn.boundStatus } + // 目标列不绑定 => 保留原状态 (T-BD-07) + return { status: currentStatus } +} + +/** + * 场景 B: 任务状态改为 newStatus 后, 应该落在哪一列 + * @returns {{ columnId: number, moved: boolean }} + */ +export function columnOnStatusChange(task, newStatus, columns) { + // 已匹配 (当前列就绑定该状态) => 原地不动 + if (task.column && task.column.boundStatus === newStatus) { + return { columnId: task.columnId, moved: false } + } + // 按当前列顺序取第一个绑定该状态的列 (T-BD-06, T-BD-15) + const ordered = sortColumns(columns || []) + const target = ordered.find((c) => c.boundStatus === newStatus) + if (target) return { columnId: target.id, moved: target.id !== task.columnId } + // 无匹配列 => 不移动, 保留原列 (T-BD-16) + return { columnId: task.columnId, moved: false } +} + +/** 子任务豁免: 不参与任何列/状态联动 (T-BD-13) */ +export function isSubtask(task) { + return task.parentId != null +} + +/** + * 统一裁决入口: 写接口调用它算出最终的 status 与 columnId + * + * @param {object} task 现有任务 (含 columnId, status, parentId, column) + * @param {object} patch 本次提交 { columnId?, status? } + * @param {object[]} columns 项目全部列 + * @returns {{ status: string, columnId: number }} + */ +export function resolveTaskWrite(task, patch, columns) { + let status = patch.status !== undefined ? patch.status : task.status + let columnId = patch.columnId !== undefined ? patch.columnId : task.columnId + + // 子任务: 原样落库 + if (isSubtask(task)) return { status, columnId } + + const ordered = sortColumns(columns || []) + const targetColumn = ordered.find((c) => c.id === columnId) + + // 场景 A: 显式换列 + if (patch.columnId !== undefined && targetColumn) { + status = statusOnColumnChange(status, targetColumn).status + } + + // 场景 B: 状态变了(或换列后状态变了) => 检查是否需要归位 + // 注意: 只在"状态发生变化"或"显式改状态"时才归位, 单纯拖拽已由 A 处理 + if (patch.status !== undefined) { + const next = columnOnStatusChange({ ...task, columnId, column: targetColumn }, status, ordered) + columnId = next.columnId + } + + return { status, columnId } +} diff --git a/server/task-domain/retention.js b/server/task-domain/retention.js new file mode 100644 index 0000000..819b219 --- /dev/null +++ b/server/task-domain/retention.js @@ -0,0 +1,31 @@ +// 任务模块: 保留选项的可见性过滤 (T-FL-*) +// 前端 src/pages/task/date.js 有一份同口径实现, 用于即时过滤; 后端用于统计/兜底 +import { isVisibleByRetention } from './date.js' + +/** + * 按"任务所属列"的保留选项过滤任务列表 (T-FL-11) + * @param {object[]} tasks 任务(需含 status / doneAt / columnId) + * @param {object[]} columns 列(需含 id / retention) + */ +export function filterVisible(tasks, columns, now = new Date()) { + const retentionById = new Map((columns || []).map((c) => [c.id, c.retention || 'forever'])) + return (tasks || []).filter((t) => { + const opt = retentionById.get(t.columnId) || 'forever' + return isVisibleByRetention(t, opt, now) + }) +} + +/** 单条判定, 供详情接口复用 */ +export function isTaskVisible(task, columns, now = new Date()) { + const col = (columns || []).find((c) => c.id === task.columnId) + return isVisibleByRetention(task, col?.retention || 'forever', now) +} + +/** 列内计数: 过滤后条数 (T-FL-14 计数与展示同源) */ +export function countByColumn(tasks, columns, now = new Date()) { + const visible = filterVisible(tasks, columns, now) + const out = {} + for (const c of columns || []) out[c.id] = 0 + for (const t of visible) out[t.columnId] = (out[t.columnId] || 0) + 1 + return out +} diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 7f1a392..1f6ca45 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -19,15 +19,15 @@ dependencies = [ [[package]] name = "alloc-no-stdlib" -version = "2.0.4" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" +checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a" [[package]] name = "alloc-stdlib" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1" dependencies = [ "alloc-no-stdlib", ] @@ -47,6 +47,137 @@ version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f8abc12baad266b1c8cec146854c195b5864b4221d4b2ca7296a7ae82d9e451" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "atk" version = "0.18.2" @@ -76,6 +207,20 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "auto-launch" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17918dba7ecf78b9a14507ec9f984e7977d13fad87dc86d61038980a45d128cf" +dependencies = [ + "dirs 6.0.0", + "os_info", + "smappservice-rs", + "thiserror 2.0.19", + "windows-registry", + "windows-result", +] + [[package]] name = "autocfg" version = "1.5.1" @@ -94,6 +239,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "bit-set" version = "0.8.0" @@ -143,10 +294,23 @@ dependencies = [ ] [[package]] -name = "brotli" -version = "8.0.4" +name = "blocking" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" +checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "brotli" +version = "9.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6737ce8b81460bf9407909f73ff50bf13f7cb8577e1e76715f57ed0eba58ba44" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -155,9 +319,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "5.0.3" +version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" +checksum = "34c6783f42c21c952ad7112b354f0ba2f10760e9decea5b1b60523d6e270faaf" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -190,6 +354,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.12.1" @@ -258,12 +428,13 @@ dependencies = [ [[package]] name = "cargo_toml" -version = "0.22.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +checksum = "82f4b26e751e711a5302649417f2da046dce6391b2ea30a4820f37462314f0b9" dependencies = [ + "semver", "serde", - "toml 0.9.12+spec-1.1.0", + "toml 1.1.4+spec-1.1.0", ] [[package]] @@ -284,13 +455,13 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cfb" -version = "0.7.3" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +checksum = "a347dcabdae9c31b0825fd6a8bed285ec9c2acb89c47827126d52fa4f59cece3" dependencies = [ - "byteorder", "fnv", "uuid", + "web-time", ] [[package]] @@ -335,7 +506,7 @@ dependencies = [ "iana-time-zone", "num-traits", "serde", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -348,6 +519,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "cookie" version = "0.18.1" @@ -462,9 +642,9 @@ dependencies = [ [[package]] name = "cssparser" -version = "0.36.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +checksum = "8c9cdaae01d5ed7882b04d795e7f752f46ff52d2fa3b50a20d28c464510bba98" dependencies = [ "cssparser-macros", "dtoa-short", @@ -475,29 +655,19 @@ dependencies = [ [[package]] name = "cssparser-macros" -version = "0.6.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +checksum = "d045de693cb712d0b22c6a64be5b953f67b3ce00ab5ad3dd5d8b441886ab8e1a" dependencies = [ "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] name = "ctor" -version = "0.8.0" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" -dependencies = [ - "ctor-proc-macro", - "dtor", -] - -[[package]] -name = "ctor-proc-macro" -version = "0.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" +checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" [[package]] name = "darling" @@ -593,6 +763,15 @@ dependencies = [ "dirs-sys", ] +[[package]] +name = "dirs" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d57d423b3c82e89b9a24ca3091fee61f456a26edbd28d26c65906f4bc1dcd8f" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-sys" version = "0.5.0" @@ -653,9 +832,9 @@ dependencies = [ [[package]] name = "dom_query" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +checksum = "fac5fca71e65e94cc718a6e2af65d6e0f9c6027751c2aa562fbb5087fda639bc" dependencies = [ "bit-set", "cssparser", @@ -690,21 +869,6 @@ dependencies = [ "dtoa", ] -[[package]] -name = "dtor" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" -dependencies = [ - "dtor-proc-macro", -] - -[[package]] -name = "dtor-proc-macro" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" - [[package]] name = "dunce" version = "1.0.5" @@ -746,6 +910,33 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -773,6 +964,26 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.5.0" @@ -919,6 +1130,19 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.33" @@ -1297,6 +1521,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + [[package]] name = "hex" version = "0.4.3" @@ -1305,9 +1535,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "html5ever" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8" dependencies = [ "log", "markup5ever", @@ -1442,7 +1672,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -1456,23 +1686,26 @@ dependencies = [ [[package]] name = "iboard" -version = "0.1.0" +version = "0.2.1" dependencies = [ "serde", "serde_json", "tauri", "tauri-build", + "tauri-plugin-autostart", "tauri-plugin-hotswap", + "tauri-plugin-single-instance", + "windows-sys 0.60.2", ] [[package]] name = "ico" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" +checksum = "d75e94d66081e4d8dcd098bd8dc5254800fea14a26389b298410158d834de167" dependencies = [ "byteorder", - "png 0.17.16", + "png", ] [[package]] @@ -1584,6 +1817,19 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -1609,9 +1855,9 @@ dependencies = [ [[package]] name = "infer" -version = "0.19.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +checksum = "f4200d433cbd5178df7797c9c2e75b348b728e39631cf14520d1e2fc424201f4" dependencies = [ "cfb", ] @@ -1708,21 +1954,21 @@ dependencies = [ [[package]] name = "json-patch" -version = "3.0.1" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +checksum = "7421438de105a0827e44fadd05377727847d717c80ce29a229f85fd04c427b72" dependencies = [ "jsonptr", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.19", ] [[package]] name = "jsonptr" -version = "0.6.3" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +checksum = "a5a3cc660ba5d72bce0b3bb295bf20847ccbb40fd423f3f05b61273672e561fe" dependencies = [ "serde", "serde_json", @@ -1730,13 +1976,12 @@ dependencies = [ [[package]] name = "keyboard-types" -version = "0.7.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d" dependencies = [ "bitflags 2.13.1", "serde", - "unicode-segmentation", ] [[package]] @@ -1832,9 +2077,9 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "markup5ever" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de" dependencies = [ "log", "tendril", @@ -1890,10 +2135,20 @@ dependencies = [ ] [[package]] -name = "muda" -version = "0.19.3" +name = "moxcms" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "muda" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca139e57da4383727f189e43e66e84fa372347027fc996f9f1a007ec0a37996" dependencies = [ "crossbeam-channel", "dpi", @@ -1904,7 +2159,7 @@ dependencies = [ "objc2-core-foundation", "objc2-foundation", "once_cell", - "png 0.18.1", + "png", "serde", "thiserror 2.0.19", "windows-sys 0.61.2", @@ -1942,6 +2197,12 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + [[package]] name = "ndk-sys" version = "0.6.0+11769913" @@ -1957,6 +2218,18 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "num-conv" version = "0.2.2" @@ -2015,6 +2288,7 @@ dependencies = [ "objc2", "objc2-core-foundation", "objc2-foundation", + "objc2-quartz-core", ] [[package]] @@ -2117,6 +2391,7 @@ checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ "bitflags 2.13.1", "block2", + "libc", "objc2", "objc2-core-foundation", ] @@ -2144,6 +2419,30 @@ dependencies = [ "objc2-foundation", ] +[[package]] +name = "objc2-security" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" +dependencies = [ + "bitflags 2.13.1", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-service-management" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b213642d6959cc6023ceb1217aa595eaaf09b8094ce95127c103cab611fe65e8" +dependencies = [ + "block2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-security", +] + [[package]] name = "objc2-ui-kit" version = "0.3.2" @@ -2244,6 +2543,31 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "os_info" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf20a545b305cf1da722b236b5155c9bb35f1d5ceb28c048bd96ca842f41b5b" +dependencies = [ + "android_system_properties", + "log", + "nix", + "objc2", + "objc2-foundation", + "objc2-ui-kit", + "windows-sys 0.61.2", +] + [[package]] name = "pango" version = "0.18.3" @@ -2269,6 +2593,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -2289,7 +2619,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -2357,6 +2687,17 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.33" @@ -2376,19 +2717,6 @@ dependencies = [ "time", ] -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - [[package]] name = "png" version = "0.18.1" @@ -2402,6 +2730,20 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "potential_utf" version = "0.1.5" @@ -2485,6 +2827,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + [[package]] name = "quick-xml" version = "0.41.0" @@ -2942,9 +3290,9 @@ dependencies = [ [[package]] name = "selectors" -version = "0.36.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +checksum = "8adfa1c298912827b8a28b223b3b874357397ae706e6190acd9bf28cee99114d" dependencies = [ "bitflags 2.13.1", "cssparser", @@ -3156,6 +3504,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "simd-adler32" version = "0.3.10" @@ -3180,6 +3538,18 @@ version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +[[package]] +name = "smappservice-rs" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52703b97a53101cf5d4580e0737aaa634ce1fdcfc123c16b2a9658e358013488" +dependencies = [ + "objc2", + "objc2-foundation", + "objc2-service-management", + "thiserror 2.0.19", +] + [[package]] name = "socket2" version = "0.6.5" @@ -3244,6 +3614,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_vcruntime" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff7589a1859b09232522bb9edf076ca7fcf14ded96685d3fa629db73023ffbb7" + [[package]] name = "string_cache" version = "0.9.0" @@ -3282,9 +3658,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "swift-rs" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +checksum = "cefd87076fd385308ee4aff597256c902f1fbb4d031de1558efc12f8ffefadd9" dependencies = [ "base64 0.21.7", "serde", @@ -3379,9 +3755,9 @@ dependencies = [ [[package]] name = "tao" -version = "0.35.3" +version = "0.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" +checksum = "d5777b06fe6435126ae8d816eba269e050e1e10d302cf5461e9c49eccabc3548" dependencies = [ "bitflags 2.13.1", "block2", @@ -3399,12 +3775,12 @@ dependencies = [ "libc", "log", "ndk", + "ndk-context", "ndk-sys", "objc2", "objc2-app-kit", "objc2-foundation", "objc2-ui-kit", - "once_cell", "parking_lot", "percent-encoding", "raw-window-handle", @@ -3412,7 +3788,7 @@ dependencies = [ "unicode-segmentation", "url", "windows", - "windows-core 0.61.2", + "windows-core", "windows-version", "x11-dl", ] @@ -3447,14 +3823,14 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.11.5" +version = "2.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "667b20e2726d572dea2de7370da16e188eb06008faf9a92fab7cdc46791190b5" +checksum = "5fb0244332cfb9cee7e2a568bf5da29a36ed8fdbd717f6f47a66b6b7b358c072" dependencies = [ "anyhow", "bytes", "cookie", - "dirs", + "dirs 7.0.0", "dunce", "embed_plist", "getrandom 0.3.4", @@ -3462,6 +3838,7 @@ dependencies = [ "gtk", "heck 0.5.0", "http", + "image", "jni", "libc", "log", @@ -3498,13 +3875,13 @@ dependencies = [ [[package]] name = "tauri-build" -version = "2.6.3" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632" +checksum = "adddb0e648752aaac5da9c3c3c6a68e1e87cab55e3b757b18a9441ef8c3ac614" dependencies = [ "anyhow", "cargo_toml", - "dirs", + "dirs 7.0.0", "glob", "heck 0.5.0", "json-patch", @@ -3512,6 +3889,8 @@ dependencies = [ "semver", "serde", "serde_json", + "static_vcruntime", + "strsim", "tauri-utils", "tauri-winres", "walkdir", @@ -3519,16 +3898,16 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.6.3" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5" +checksum = "fb37631cf71dab5e8ab55e1bdd701e18a52653d90b78e71d0e5598b1e0a61316" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "brotli", "ico", "json-patch", "plist", - "png 0.17.16", + "png", "proc-macro2", "quote", "semver", @@ -3546,9 +3925,9 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.6.3" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45" +checksum = "0cc38db58854c10fb5f91a887ed8bf82d3239bb92b5d8db86eda09e96d9963d1" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -3560,9 +3939,9 @@ dependencies = [ [[package]] name = "tauri-plugin" -version = "2.6.3" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74be5dd4bed9afbd145e5716b5fa2ec28cbc29c34ffa61c258c9273d896c8020" +checksum = "67fb21a77e8f94e8beb3325e583102488659365af0a3948b7ea244c5b779c291" dependencies = [ "anyhow", "glob", @@ -3574,6 +3953,20 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-autostart" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bce56ceb0a99b8ab5eebc977ba9ffc62391b862648cb9ab76fc8bc7231b8f7e2" +dependencies = [ + "auto-launch", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.19", +] + [[package]] name = "tauri-plugin-hotswap" version = "0.0.4" @@ -3581,7 +3974,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3550b0b6727104637a12c6c1c5270714d4a8d935de3b2b71a5fe5a111d7ffc5" dependencies = [ "base64 0.22.1", - "dirs", + "dirs 6.0.0", "flate2", "log", "minisign-verify", @@ -3598,10 +3991,26 @@ dependencies = [ ] [[package]] -name = "tauri-runtime" -version = "2.11.3" +name = "tauri-plugin-single-instance" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0b4bc95aed361b0019067d189a1174a603d460d0f6c72606512d59fc9c12ec8" +checksum = "561cd99484561bc39c954768e7b41a66cebbe1ac3d9d081c34361f740c489e2c" +dependencies = [ + "serde", + "serde_json", + "tauri", + "thiserror 2.0.19", + "tokio", + "tracing", + "windows-sys 0.61.2", + "zbus", +] + +[[package]] +name = "tauri-runtime" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c55766d99cea69055904d1735b93c2049302b2e09effd462120deb5581d78e9" dependencies = [ "cookie", "dpi", @@ -3624,9 +4033,9 @@ dependencies = [ [[package]] name = "tauri-runtime-wry" -version = "2.11.4" +version = "2.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f" +checksum = "e1aa9d8b7a120f879f44fdc1053f7b7dbf416bf0bbc6a72b714dec46b782b52f" dependencies = [ "gtk", "http", @@ -3634,7 +4043,6 @@ dependencies = [ "log", "objc2", "objc2-app-kit", - "once_cell", "percent-encoding", "raw-window-handle", "softbuffer", @@ -3650,9 +4058,9 @@ dependencies = [ [[package]] name = "tauri-utils" -version = "2.9.3" +version = "2.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" +checksum = "362ab21cd4cc713f2a02b1a9b5688198b1541cfb6907c4281e9190e0f0ef4aa3" dependencies = [ "anyhow", "brotli", @@ -3874,21 +4282,6 @@ dependencies = [ "toml_edit 0.20.2", ] -[[package]] -name = "toml" -version = "0.9.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" -dependencies = [ - "indexmap 2.14.0", - "serde_core", - "serde_spanned 1.1.1", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 0.7.15", -] - [[package]] name = "toml" version = "1.1.4+spec-1.1.0" @@ -3913,15 +4306,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -4034,9 +4418,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "tracing-core" version = "0.1.36" @@ -4048,12 +4444,12 @@ dependencies = [ [[package]] name = "tray-icon" -version = "0.24.2" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "045979e3f037cd18ad1cb2a419dfda133c5c29c9f3453370079f2255d46c257e" +checksum = "b2b9c52859a94554803ccd4a24b98f74148ebc73b90676d783f3490b1bff9d72" dependencies = [ "crossbeam-channel", - "dirs", + "dirs 7.0.0", "libappindicator", "muda", "objc2", @@ -4062,7 +4458,7 @@ dependencies = [ "objc2-core-graphics", "objc2-foundation", "once_cell", - "png 0.18.1", + "png", "serde", "thiserror 2.0.19", "windows-sys 0.61.2", @@ -4087,44 +4483,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] -name = "unic-char-property" -version = "0.9.0" +name = "uds_windows" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", + "memoffset", + "tempfile", + "windows-sys 0.61.2", ] [[package]] @@ -4160,13 +4526,13 @@ dependencies = [ [[package]] name = "urlpattern" -version = "0.3.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +checksum = "df16f50ef4cc145211879a3867ba757076b25dfee812040dcb0658bd9ae7904b" dependencies = [ + "icu_properties", "regex", "serde", - "unic-ucd-ident", "url", ] @@ -4428,16 +4794,14 @@ dependencies = [ [[package]] name = "webview2-com" -version = "0.38.2" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" +checksum = "3f89fca7a704cee10dcb3654c1dbb8941d1783132f1917358af75bec37a7d7e6" dependencies = [ "webview2-com-macros", "webview2-com-sys", "windows", - "windows-core 0.61.2", - "windows-implement", - "windows-interface", + "windows-core", ] [[package]] @@ -4453,13 +4817,13 @@ dependencies = [ [[package]] name = "webview2-com-sys" -version = "0.38.2" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" +checksum = "b3a07132775117d6065853d9d1178157b8c90e228de47129d6bce2c7edebedfb" dependencies = [ "thiserror 2.0.19", "windows", - "windows-core 0.61.2", + "windows-core", ] [[package]] @@ -4495,52 +4859,39 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "window-vibrancy" -version = "0.6.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +checksum = "111e51caca442cafd9bab396628ac4d814880eaea4e63dfd76a12e9f342b5580" dependencies = [ "objc2", "objc2-app-kit", "objc2-core-foundation", "objc2-foundation", + "objc2-quartz-core", "raw-window-handle", - "windows-sys 0.59.0", + "windows-sys 0.61.2", "windows-version", ] [[package]] name = "windows" -version = "0.61.3" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ "windows-collections", - "windows-core 0.61.2", + "windows-core", "windows-future", - "windows-link 0.1.3", "windows-numerics", ] [[package]] name = "windows-collections" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-core 0.61.2", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-core", ] [[package]] @@ -4551,19 +4902,19 @@ checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] name = "windows-future" -version = "0.2.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", + "windows-core", + "windows-link", "windows-threading", ] @@ -4589,12 +4940,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" @@ -4603,12 +4948,12 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-numerics" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", + "windows-core", + "windows-link", ] [[package]] @@ -4617,18 +4962,9 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] @@ -4637,16 +4973,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", + "windows-link", ] [[package]] @@ -4655,7 +4982,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -4685,13 +5012,22 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + [[package]] name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -4718,7 +5054,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", @@ -4726,12 +5062,29 @@ dependencies = [ ] [[package]] -name = "windows-threading" -version = "0.1.0" +name = "windows-targets" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.1.3", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", ] [[package]] @@ -4740,7 +5093,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -4755,6 +5108,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -4767,6 +5126,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -4779,12 +5144,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -4797,6 +5174,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -4809,6 +5192,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -4821,6 +5210,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -4833,6 +5228,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" version = "0.5.40" @@ -4842,12 +5243,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winnow" -version = "0.7.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" - [[package]] name = "winnow" version = "1.0.4" @@ -4881,15 +5276,15 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wry" -version = "0.55.1" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" +checksum = "759c50ddad0ef9913bb5496cb9d509a50433a3b78cb0238d61105526589cf0e9" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "block2", "cookie", "crossbeam-channel", - "dirs", + "dirs 7.0.0", "dom_query", "dpi", "dunce", @@ -4906,7 +5301,6 @@ dependencies = [ "objc2-foundation", "objc2-ui-kit", "objc2-web-kit", - "once_cell", "percent-encoding", "raw-window-handle", "sha2", @@ -4918,7 +5312,7 @@ dependencies = [ "webkit2gtk-sys", "webview2-com", "windows", - "windows-core 0.61.2", + "windows-core", "windows-version", "x11-dl", ] @@ -4977,6 +5371,76 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zbus" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 1.0.4", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 3.0.3", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812a12be4ead7a59e18ea8f047d08add0066b73bc62fc1467066a2472ae088dd" +dependencies = [ + "serde", + "winnow 1.0.4", + "zvariant", +] + +[[package]] +name = "zcheapstr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473" +dependencies = [ + "serde", +] + [[package]] name = "zerofrom" version = "0.1.8" @@ -5042,3 +5506,44 @@ name = "zmij" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zvariant" +version = "5.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80bf6d6cd6a4b601acb64cfb8284cdc643fcfa75ef26ae3c46ec8aa4e8a1fbb7" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 1.0.4", + "zcheapstr", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2672456ba1cb54b5eaec136e63135b3c7495fb938e53b28a62e633496d486d" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 3.0.3", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 3.0.3", + "winnow 1.0.4", +] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 71f3739..b274c56 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iboard" -version = "0.1.0" +version = "0.2.1" description = "iBoard - 经济数据与策略" authors = ["iBoard Team"] edition = "2021" @@ -27,6 +27,20 @@ tauri-plugin-hotswap = "0.0.4" [target."cfg(target_os = \"android\")".dependencies] tauri = { version = "2", features = [] } +# Windows 外壳能力 (W-M1~M4): 托盘 / 自启动 / 单实例 / Win32 辅助 +# 仅加在 Windows 目标上, Android 构建不受影响 (W-NF-05) +[target."cfg(windows)".dependencies] +tauri = { version = "2", features = ["tray-icon", "image-png"] } +tauri-plugin-autostart = "2" +tauri-plugin-single-instance = "2" +windows-sys = { version = "0.60", features = [ + "Win32_Foundation", + "Win32_Graphics_Gdi", + "Win32_System_Registry", + "Win32_UI_Shell", + "Win32_UI_WindowsAndMessaging", +] } + [features] # Tauri 的"自定义协议"模式让打包后的 webview 能从 tauri:// 加载资源 # 用于生产构建, dev 模式不需要 diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png index 145eb8a..f3c5bcf 100644 Binary files a/src-tauri/icons/128x128.png and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png index 95a986b..f89781c 100644 Binary files a/src-tauri/icons/128x128@2x.png and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png index 1bc47b2..5da1844 100644 Binary files a/src-tauri/icons/32x32.png and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/64x64.png b/src-tauri/icons/64x64.png new file mode 100644 index 0000000..0dde31b Binary files /dev/null and b/src-tauri/icons/64x64.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000..3354325 Binary files /dev/null and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000..079f462 Binary files /dev/null and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000..e93f9b8 Binary files /dev/null and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000..c03af83 Binary files /dev/null and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000..f328746 Binary files /dev/null and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000..c529095 Binary files /dev/null and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000..813fe01 Binary files /dev/null and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000..6e20681 Binary files /dev/null and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000..5e7d527 Binary files /dev/null and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000..df7d3c7 Binary files /dev/null and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml b/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..2ffbf24 --- /dev/null +++ b/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..b147d29 Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..1645beb Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..eb46e90 Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..8372b96 Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..cbfdf38 Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..16101ec Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..3a3ccea Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..e78c690 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..9fbf717 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..943bcb2 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..3f72e4f Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..a54aed6 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..63ba0d8 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..d6422bb Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..0d0186a Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/values/ic_launcher_background.xml b/src-tauri/icons/android/values/ic_launcher_background.xml new file mode 100644 index 0000000..ea9c223 --- /dev/null +++ b/src-tauri/icons/android/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #fff + \ No newline at end of file diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns index 067233f..aecff57 100644 Binary files a/src-tauri/icons/icon.icns and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico index 95a986b..c9dd1dc 100644 Binary files a/src-tauri/icons/icon.ico and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png index 067233f..8e57d01 100644 Binary files a/src-tauri/icons/icon.png and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@1x.png b/src-tauri/icons/ios/AppIcon-20x20@1x.png new file mode 100644 index 0000000..d09ccd8 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png new file mode 100644 index 0000000..811c4d4 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x.png b/src-tauri/icons/ios/AppIcon-20x20@2x.png new file mode 100644 index 0000000..811c4d4 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@3x.png b/src-tauri/icons/ios/AppIcon-20x20@3x.png new file mode 100644 index 0000000..c1790e8 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@1x.png b/src-tauri/icons/ios/AppIcon-29x29@1x.png new file mode 100644 index 0000000..a6d8d0c Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 0000000..abaf338 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x.png b/src-tauri/icons/ios/AppIcon-29x29@2x.png new file mode 100644 index 0000000..abaf338 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@3x.png b/src-tauri/icons/ios/AppIcon-29x29@3x.png new file mode 100644 index 0000000..e89c378 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@1x.png b/src-tauri/icons/ios/AppIcon-40x40@1x.png new file mode 100644 index 0000000..811c4d4 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png new file mode 100644 index 0000000..2f8bbb1 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x.png b/src-tauri/icons/ios/AppIcon-40x40@2x.png new file mode 100644 index 0000000..2f8bbb1 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@3x.png b/src-tauri/icons/ios/AppIcon-40x40@3x.png new file mode 100644 index 0000000..752956f Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-512@2x.png b/src-tauri/icons/ios/AppIcon-512@2x.png new file mode 100644 index 0000000..bc42423 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-512@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@2x.png b/src-tauri/icons/ios/AppIcon-60x60@2x.png new file mode 100644 index 0000000..752956f Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@3x.png b/src-tauri/icons/ios/AppIcon-60x60@3x.png new file mode 100644 index 0000000..47d80fd Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@1x.png b/src-tauri/icons/ios/AppIcon-76x76@1x.png new file mode 100644 index 0000000..333da4b Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@2x.png b/src-tauri/icons/ios/AppIcon-76x76@2x.png new file mode 100644 index 0000000..81c7717 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 0000000..c63e868 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/src-tauri/icons/tray-dark.png b/src-tauri/icons/tray-dark.png new file mode 100644 index 0000000..a8842c4 Binary files /dev/null and b/src-tauri/icons/tray-dark.png differ diff --git a/src-tauri/icons/tray-light.png b/src-tauri/icons/tray-light.png new file mode 100644 index 0000000..0ead4c9 Binary files /dev/null and b/src-tauri/icons/tray-light.png differ diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 4e99313..bc40ab1 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,9 +1,14 @@ // 跨平台入口: 桌面从这里走, Android 走 mobile_entry_point 宏 // 启动流程: -// 1) 初始化 tauri-plugin-hotswap (前端 OTA 升级插件) +// 1) Windows 外壳能力注册 (单实例 / 自启动 / 托盘 / 吸附悬浮球, 见 src/shell) +// 2) 初始化 tauri-plugin-hotswap (前端 OTA 升级插件) // - 必须在 build 之前调用, 插件会替换 tauri::generate_context! 的 asset provider, // 后续可被 tauri::Builder 消费 -// 2) 业务初始化 +// 3) 业务初始化 + +#[cfg(all(desktop, target_os = "windows"))] +mod shell; + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { // 生成 context, 交给 hotswap 包装 (替换 Assets provider, 让 tauri://localhost 资源 @@ -12,8 +17,94 @@ pub fn run() { let (hotswap, context) = tauri_plugin_hotswap::init(context) .expect("failed to initialize tauri-plugin-hotswap"); - tauri::Builder::default() + let mut builder = tauri::Builder::default() + .invoke_handler(tauri::generate_handler![ + dock_hover_start, + dock_hover_end, + dock_click, + dock_drag_start, + dock_drag_move, + dock_drag_end, + ]) + .on_window_event(on_window_event); + + #[cfg(all(desktop, target_os = "windows"))] + { + builder = builder + // 单实例必须最先注册 (W-TRY-07): 二次启动唤回已有实例 + .plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| { + shell::activate_primary(app); + })) + .plugin(tauri_plugin_autostart::init( + tauri_plugin_autostart::MacosLauncher::LaunchAgent, + Some(vec!["--autostart"]), + )) + .setup(|app| shell::setup(app)); + } + + builder .plugin(hotswap) .run(context) .expect("error while running iBoard"); } + +fn on_window_event(window: &tauri::Window, event: &tauri::WindowEvent) { + #[cfg(all(desktop, target_os = "windows"))] + shell::on_window_event(window, event); + #[cfg(not(all(desktop, target_os = "windows")))] + { + let _ = (window, event); + } +} + +// —— 悬浮球 IPC (dock.html → Rust); 非 Windows 桌面平台为 no-op —— + +#[tauri::command] +fn dock_hover_start(app: tauri::AppHandle) { + #[cfg(all(desktop, target_os = "windows"))] + shell::dock_hover_start(&app); + #[cfg(not(all(desktop, target_os = "windows")))] + let _ = app; +} + +#[tauri::command] +fn dock_hover_end(app: tauri::AppHandle) { + #[cfg(all(desktop, target_os = "windows"))] + shell::dock_hover_end(&app); + #[cfg(not(all(desktop, target_os = "windows")))] + let _ = app; +} + +#[tauri::command] +fn dock_click(app: tauri::AppHandle) { + #[cfg(all(desktop, target_os = "windows"))] + shell::dock_click(&app); + #[cfg(not(all(desktop, target_os = "windows")))] + let _ = app; +} + +#[tauri::command] +fn dock_drag_start(app: tauri::AppHandle) { + #[cfg(all(desktop, target_os = "windows"))] + shell::dock_drag_start(&app); + #[cfg(not(all(desktop, target_os = "windows")))] + let _ = app; +} + +#[tauri::command] +fn dock_drag_move(app: tauri::AppHandle, dx: i32, dy: i32) { + #[cfg(all(desktop, target_os = "windows"))] + shell::dock_drag_move(&app, dx, dy); + #[cfg(not(all(desktop, target_os = "windows")))] + { + let _ = (app, dx, dy); + } +} + +#[tauri::command] +fn dock_drag_end(app: tauri::AppHandle) { + #[cfg(all(desktop, target_os = "windows"))] + shell::dock_drag_end(&app); + #[cfg(not(all(desktop, target_os = "windows")))] + let _ = app; +} diff --git a/src-tauri/src/shell/config.rs b/src-tauri/src/shell/config.rs new file mode 100644 index 0000000..ff8e9d8 --- /dev/null +++ b/src-tauri/src/shell/config.rs @@ -0,0 +1,160 @@ +// 澶栧3璁剧疆鎸佷箙鍖?(W-CFG-01~03): %APPDATA%\com.iboard.app\shell-config.json +// - 涓庡墠绔处鍙?鏈嶅姟绔棤鍏? 绂荤嚎鍙敤; +// - 瀛楁缂哄け/鎹熷潖鏃跺洖閫€榛樿鍊?(serde(default) + 瀹芥澗鍔犺浇); +// - 鍐欏叆閲囩敤 涓存椂鏂囦欢 + 鏇挎崲 鐨勫師瀛愯涔夈€? +use serde::{Deserialize, Serialize}; +use std::path::Path; + +pub const FILE_NAME: &str = "shell-config.json"; + +/// 璁捐鍩哄噯灏哄 (绉诲姩绔竷灞€鍙e緞): 绐楀彛姣斾緥閿佹涓?405:720 +pub const DESIGN_WIDTH: f64 = 405.0; +pub const DESIGN_HEIGHT: f64 = 720.0; +/// 窗口宽度可配置范围 (逻辑像素); 高度由比例推导, 不接受单独配置 +pub const WIDTH_MIN: f64 = 242.0; +pub const WIDTH_MAX: f64 = 620.0; + +/// 鎸夐攣瀹氭瘮渚嬬敱瀹藉害鎺ㄥ楂樺害 (鍞竴灏哄鍏ュ彛) +pub fn height_for_width(width: f64) -> f64 { + let w = width.clamp(WIDTH_MIN, WIDTH_MAX); + (w * DESIGN_HEIGHT / DESIGN_WIDTH).round() +} + +/// 鍚搁檮杈?(W-SNP-01: 鍥涜竟) +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "lowercase")] +pub enum Edge { + #[default] + Left, + Right, + Top, + Bottom, +} + +impl Edge { + pub fn css(&self) -> &'static str { + match self { + Edge::Left => "left", + Edge::Right => "right", + Edge::Top => "top", + Edge::Bottom => "bottom", + } + } +} + +/// 涓荤獥鍙h蹇?(W-WIN-07): 灏哄涓洪€昏緫鍍忕礌 (DPI 绋冲畾), 浣嶇疆涓虹墿鐞嗗儚绱?(鍔犺浇鏃舵牎楠屽す鍙? +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(default)] +pub struct WindowCfg { + pub width: f64, + pub height: f64, + pub x: Option, + pub y: Option, +} + +impl Default for WindowCfg { + fn default() -> Self { + // 323x574 = 405:720 姣斾緥 (0.797x of 405x720, 2026-10-26 鏀惧ぇ 12%) + Self { width: 323.0, height: height_for_width(323.0), x: None, y: None } + } +} + +/// 鎮诞鐞冨仠闈?(W-SNP-02/12): 杈?+ 娌胯竟浣嶇疆 (鎮诞鐞冧腑蹇? 鐗╃悊鍍忕礌) +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(default)] +pub struct DockCfg { + pub edge: Edge, + pub offset: i32, +} + +impl Default for DockCfg { + fn default() -> Self { + Self { edge: Edge::Left, offset: 360 } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(default)] +pub struct ShellConfig { + /// 鍚搁檮鎬诲紑鍏?(W-SNP-09, 榛樿寮€鍚? + pub snap_enabled: bool, + pub window: WindowCfg, + pub dock: DockCfg, + /// 绐楀彛瀹藉害 (閫昏緫鍍忕礌) 鈥斺€?**鍞竴灏哄閰嶇疆椤?*銆? /// 楂樺害鎸?405:720 閿佸畾姣斾緥鑷姩鎺ㄥ, 鏃犻渶涔熸棤娉曞崟鐙厤缃? + /// 鐣欑┖鏃跺洖钀藉埌 window.width銆? #[serde(default)] + pub window_width: Option, + /// 閰嶇疆缁撴瀯鐗堟湰: 0 = 鏃х増 (绐楀彛灏哄鎸?405x720 鍙e緞淇濆瓨), + /// 1 = 鏁翠綋缂╁皬 30% (288 鍙e緞), 2 = 鏀惧ぇ 12% (323 鍙e緞) + 鍗曞€煎搴﹂厤缃€? /// load 鏃跺仛涓€娆℃€ц縼绉汇€? /// 娉ㄦ剰蹇呴』鐢ㄥ瓧娈电骇 serde(default) (u8 鈫?0): 瀹瑰櫒绾?default 浼氬彇 + /// ShellConfig::default() 鐨?2, 鏃ч厤缃枃浠跺氨姘歌繙涓嶈縼绉讳簡銆? #[serde(default)] + pub cfg_version: u8, +} + +impl Default for ShellConfig { + fn default() -> Self { + Self { + snap_enabled: true, + window: WindowCfg::default(), + dock: DockCfg::default(), + window_width: Some(WindowCfg::default().width), + cfg_version: 2, + } + } +} + +/// 杩佺Щ鍓嶇殑绐楀彛瀹藉害 (鏃у彛寰?, 渚?load 浣跨敤 +impl WindowCfg { + /// 搴旂敤閿佸畾姣斾緥: 瀹藉害澶瑰彇鍒板悎娉曡寖鍥? 楂樺害鐢卞搴︽帹瀵?(蹇界暐瀛樼洏楂樺害) + pub(crate) fn normalize(&mut self) { + self.width = self.width.clamp(WIDTH_MIN, WIDTH_MAX); + self.height = height_for_width(self.width); + } +} + +impl ShellConfig { + pub fn load(dir: &Path) -> ShellConfig { + let path = dir.join(FILE_NAME); + let mut cfg = match std::fs::read_to_string(&path) { + Ok(text) => match serde_json::from_str::(&text) { + Ok(cfg) => cfg, + Err(_) => ShellConfig::default(), // 鍐欏潖 JSON 鈫?榛樿鍊?(W-CFG-03) + }, + Err(_) => ShellConfig::default(), + }; + // 一次性迁移 + if cfg.cfg_version < 1 { + // v0 (405 鍙e緞) 鈫?v1: 鏁翠綋缂╁皬 30% + cfg.window.width = (cfg.window.width * 0.7).max(216.0); + cfg.window.height = (cfg.window.height * 0.7).max(384.0); + cfg.cfg_version = 1; + } + if cfg.cfg_version < 2 { + // v1 (288 口径) -> v2: 放大 12% (323 口径), 引入单值宽度配置 + cfg.window.width = (cfg.window.width * 1.12).max(WIDTH_MIN); + cfg.window.height = height_for_width(cfg.window.width); + cfg.cfg_version = 2; + } + // 鍗曞€煎搴︿紭鍏? 楂樺害涓€寰嬫寜閿佸畾姣斾緥鎺ㄥ (蹇界暐瀛樼洏楂樺害) + if let Some(w) = cfg.window_width { + cfg.window.width = w; + } + cfg.window.normalize(); + cfg.window_width = Some(cfg.window.width); + cfg.save(dir); + cfg + } + + pub fn save(&self, dir: &Path) { + let path = dir.join(FILE_NAME); + let tmp = dir.join(format!("{FILE_NAME}.tmp")); + let text = match serde_json::to_string_pretty(self) { + Ok(t) => t, + Err(_) => return, + }; + if std::fs::write(&tmp, text).is_ok() { + // Windows 上 rename 不能覆盖已存在文件, 先移除 + let _ = std::fs::remove_file(&path); + let _ = std::fs::rename(&tmp, &path); + } + // 鍐欏け璐ヤ繚鎸侀潤榛?(W-CFG-04): 杩愯涓殑澶栧3涓嶅彈褰卞搷 + } +} diff --git a/src-tauri/src/shell/mod.rs b/src-tauri/src/shell/mod.rs new file mode 100644 index 0000000..9fc3736 --- /dev/null +++ b/src-tauri/src/shell/mod.rs @@ -0,0 +1,960 @@ +//! Windows 外壳 (需求文档 §3): 托盘常驻 / 开机自启动 / 四边吸附悬浮球 / 主题跟随 +//! +//! 仅在 `desktop + windows` 下编译 (lib.rs cfg 门控); Android 构建不含本模块。 +//! 状态机要点: +//! - 收起判定在「松开鼠标」后静默 150ms 进行 (W-SNP-01, 评审决策 #1); +//! - 悬浮球 hover 停留 ≥150ms 或单击即恢复 (W-SNP-04/05, 决策 #2); +//! - 悬浮球可沿边拖动、拖到其他边换边、拖离边缘 ≥32px 拖出主窗口 (W-SNP-12~14); +//! - 无边框全屏之上保持显示, 独占全屏自动避让 (W-SNP-15/16); +//! - 托盘与悬浮球深浅色跟随系统 (W-THE)。 + +mod config; +mod win; + +use config::{DockCfg, Edge, ShellConfig}; +use std::path::PathBuf; +use std::sync::Mutex; +use std::time::{Duration, Instant}; +use tauri::menu::{CheckMenuItem, Menu, MenuEvent, MenuItem}; +use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}; +use tauri::{AppHandle, Manager, PhysicalPosition, PhysicalSize, WebviewUrl, WebviewWindowBuilder, Wry}; + +const MAIN: &str = "main"; +const DOCK: &str = "dock"; +const TRAY_ID: &str = "iboard-tray"; + +/// 悬浮球尺寸 (逻辑像素, 附录 A): 垂直边 = 28×56, 水平边 = 56×28 +const HANDLE_SHORT: f64 = 28.0; +const HANDLE_LONG: f64 = 56.0; +/// 吸附触发阈值 (W-SNP-01) +const SNAP_PX: f64 = 8.0; +/// 解除吸附阈值 (W-SNP-06/14) +const UNDOCK_PX: f64 = 32.0; +/// 碰触恢复延时 (W-SNP-04) +const RESTORE_DELAY: Duration = Duration::from_millis(150); +/// 吸附态展开后, 鼠标移开窗口多久自动收起 (auto-hide) +const AUTOHIDE_DELAY: Duration = Duration::from_millis(400); +/// 移动静默判定窗口 (松手判定, 决策 #1) +const MOVE_QUIET: Duration = Duration::from_millis(150); +/// 程序性移动后的吸附抑制 (避免 set_position 触发立即收起) +const MOVE_SUPPRESS: Duration = Duration::from_millis(400); +/// 尺寸比例锁容差 (逻辑像素) +const RATIO_TOL: f64 = 2.0; + +type CheckItem = CheckMenuItem; + +struct Shared { + data_dir: PathBuf, + cfg: ShellConfig, + /// 主窗口处于吸附态 (含贴边可见的“已恢复”状态) + docked: bool, + /// 悬浮球当前可见 (true = 收起态) + dock_visible: bool, + /// 因独占全屏而隐藏 (W-SNP-16) + fs_hidden: bool, + /// 悬浮球拖动中 (JS pointer 事件驱动) + dragging: bool, + hover_since: Option, + last_main_move: Option, + /// 最近一次移动的 (最近边, 距边缘距离), 供解除吸附的静默判定 + last_move_eval: Option<(Edge, f64)>, + /// 是否已离开边缘条带 (拖离过程中置位; 只有 armed 才允许再次触碰收起, + /// 否则恢复到贴边位置后的第一次移动事件会立即误收) + edge_armed: bool, + /// 上一次移动事件时的窗口矩形 (越线判定用) + last_main_rect: Option, + /// 吸附态展开后, 光标移出窗口的起始时刻 (auto-hide 计时) + mouse_outside_since: Option, + /// 展开后光标是否进过窗口 (悬浮球唤回=true; 托盘唤回时光标在远处=false, + /// 未武装则永不自动收起, 否则托盘唤回会立刻缩回去) + mouse_inside_since_restore: bool, + ignore_until: Option, + theme_dark: bool, + menu_autostart: Option, + menu_snap: Option, + /// 主窗口在吸附态下的恢复位置 (贴边, 物理像素) + docked_rect: Option<(PhysicalPosition, PhysicalSize)>, +} + +type State = Mutex; + +fn state<'a>(app: &'a AppHandle) -> Option<&'a State> { + app.try_state::().map(|m| m.inner()) +} + +// ============================================================================ +// 生命周期 +// ============================================================================ + +pub fn setup(app: &mut tauri::App) -> Result<(), Box> { + let silent = std::env::args().any(|a| a == "--autostart"); // W-AUT-04 静默启动 + let data_dir = app.path().app_data_dir()?; + let _ = std::fs::create_dir_all(&data_dir); + let cfg = ShellConfig::load(&data_dir); + + let main = app.get_webview_window(MAIN).expect("main window missing"); + apply_saved_window(&main, &cfg); + + // 内容缩放跟随窗口宽度: 布局视口始终等于 405 设计宽, 窗口变大/变小只是整体等比缩放. + // 仅主窗口 —— 悬浮球窗口自绘尺寸不缩放; Android 端不经过本模块, 不受影响。 + let _ = main.set_zoom(cfg.window.width / config::DESIGN_WIDTH); + + // 悬浮球窗口 (W-SNP-02): 透明 / 置顶 / 无任务栏条目, 初始隐藏 + WebviewWindowBuilder::new(app, DOCK, WebviewUrl::App("dock.html".into())) + .title("iBoard Dock") + .decorations(false) + .transparent(true) + .shadow(false) + .resizable(false) + .always_on_top(true) + .skip_taskbar(true) + .focused(false) + .visible(false) + .build()?; + + let dark = win::system_prefers_dark(); + let (menu_autostart, menu_snap) = build_tray(app, &cfg, dark)?; + + let state = Mutex::new(Shared { + data_dir, + cfg, + docked: false, + dock_visible: false, + fs_hidden: false, + dragging: false, + hover_since: None, + last_main_move: None, + last_move_eval: None, + edge_armed: true, + last_main_rect: None, + mouse_outside_since: None, + mouse_inside_since_restore: false, + ignore_until: None, + theme_dark: dark, + menu_autostart: Some(menu_autostart), + menu_snap: Some(menu_snap), + docked_rect: None, + }); + app.manage(state); + + let handle = app.handle().clone(); + std::thread::spawn(move || checker_loop(handle)); + + if !silent { + let _ = main.show(); + let _ = main.set_focus(); + } + Ok(()) +} + +fn build_tray( + app: &tauri::App, + cfg: &ShellConfig, + dark: bool, +) -> Result<(CheckItem, CheckItem), Box> { + use tauri_plugin_autostart::ManagerExt; + let autostart_on = app.autolaunch().is_enabled().unwrap_or(false); // W-AUT-03 真实状态 + + let show = MenuItem::with_id(app, "show", "显示主窗口", true, None::<&str>)?; + let autostart = CheckMenuItem::with_id(app, "autostart", "开机启动", true, autostart_on, None::<&str>)?; + let snap = CheckMenuItem::with_id(app, "snap", "边缘吸附", true, cfg.snap_enabled, None::<&str>)?; + let quit = MenuItem::with_id(app, "quit", "退出", true, None::<&str>)?; + let menu = Menu::with_items(app, &[&show, &autostart, &snap, &quit])?; + + TrayIconBuilder::with_id(TRAY_ID) + .menu(&menu) + .show_menu_on_left_click(false) // 左键留给唤回 (W-TRY-03), 右键出菜单 (W-TRY-05) + .icon(tray_icon_image(dark)?) + .tooltip("iBoard") + .on_menu_event(on_menu_event) + .on_tray_icon_event(on_tray_event) + .build(app)?; + Ok((autostart, snap)) +} + +fn checker_loop(app: AppHandle) { + let mut tick: u64 = 0; + loop { + std::thread::sleep(Duration::from_millis(80)); + tick += 1; + let now = Instant::now(); + let action = { + let Some(st) = state(&app) else { continue }; + let mut s = st.lock().expect("shell state poisoned"); + decide(&mut s, now) + }; + match action { + Action::Restore => restore_main(&app), + Action::Undock => undock(&app, None), + Action::None => {} + } + autohide_check(&app); + if tick % 6 == 0 { + fullscreen_check(&app); // ~480ms + } + if tick % 12 == 0 { + theme_check(&app); // ~1s + } + } +} + +enum Action { + None, + Restore, + Undock, +} + +fn decide(s: &mut Shared, now: Instant) -> Action { + // 1) 移动静默判定仅用于「解除吸附」(W-SNP-05/06); 收起改为触碰即收起 + // (见 record_main_move, 修订决策 #1) + if let Some(t) = s.last_main_move { + if now.duration_since(t) >= MOVE_QUIET && s.ignore_until.map_or(true, |u| now >= u) { + s.last_main_move = None; + if s.cfg.snap_enabled { + if let Some((_edge, dist)) = s.last_move_eval { + if s.docked && !s.dock_visible && s.edge_armed && dist >= UNDOCK_PX { + return Action::Undock; // 拖离 → 解除吸附 (W-SNP-05) + } + } + } + } + } + // 2) 悬浮球碰触延时恢复 (W-SNP-04) + if s.dock_visible && !s.dragging { + if let Some(t0) = s.hover_since { + if now.duration_since(t0) >= RESTORE_DELAY { + s.hover_since = None; + return Action::Restore; + } + } + } + Action::None +} + +// ============================================================================ +// 事件入口 (lib.rs 转发) +// ============================================================================ + +pub fn on_window_event(window: &tauri::Window, event: &tauri::WindowEvent) { + if window.label() != MAIN { + return; + } + match event { + WindowEvent::CloseRequested { api, .. } => { + // W-TRY-02: 关闭到托盘, 进程不退出 + api.prevent_close(); + save_window_cfg(window.app_handle()); + let _ = window.hide(); + } + WindowEvent::Resized(size) => ratio_lock(window, *size), + WindowEvent::Moved(pos) => record_main_move(window, *pos), + _ => {} + } +} + +use tauri::WindowEvent; + +fn record_main_move(window: &tauri::Window, pos: PhysicalPosition) { + let app = window.app_handle(); + let Some(st) = state(app) else { return }; + // 程序性移动 (恢复/解除吸附的 set_position) 期间不记录, + // 否则抑制期一过会把"恢复到贴边位置"误判成用户再次贴边 (W-SNP-04) + if st + .lock() + .expect("shell state poisoned") + .ignore_until + .map_or(false, |u| Instant::now() < u) + { + return; + } + let Some(wa) = window + .hwnd() + .ok() + .map(|h| win::workarea_for_hwnd(h.0)) + .flatten() + else { + return; + }; + let Ok(size) = window.outer_size() else { return }; + let rect = rect_of(pos, size); + let (near_edge, dist) = nearest_edge(&rect, &wa); + let prev_rect = st.lock().expect("shell state poisoned").last_main_rect; + let touching = edge_touching(prev_rect.as_ref(), &rect, &wa); + + let do_collapse = { + let mut s = st.lock().expect("shell state poisoned"); + // 触碰即收起 (修订决策 #1): 拖动中窗口边缘一进入边缘条带就收起, + // 不做静默等待 —— 天然免疫 Win11 Aero Snap 抢跑与不可见边框过冲。 + // 「入带 + 越线」双条件: 停泊在边缘以外 (如压住任务栏区) 的窗口不受影响。 + match touching { + Some(edge) => { + let go = s.cfg.snap_enabled && !s.dock_visible && s.edge_armed; + if go { + s.edge_armed = false; + s.last_main_move = None; + s.last_main_rect = None; + } + go.then_some(edge) + } + None => { + s.edge_armed = true; // 离开边缘条带 → 重新武装 + s.last_main_move = Some(Instant::now()); + s.last_move_eval = Some((near_edge, dist)); + s.last_main_rect = Some(rect); + None + } + } + }; + if let Some(edge) = do_collapse { + collapse(app, edge); + } +} + +/// 9:16 比例锁 (W-WIN-02/03): 宽度为驱动, 高度 = 宽 × 16/9, 上限 = 可工作区高 × 95% +fn ratio_lock(window: &tauri::Window, actual: PhysicalSize) { + if window.is_minimized().unwrap_or(true) || actual.width == 0 || actual.height == 0 { + return; + } + let Ok(scale) = window.scale_factor() else { return }; + let wa_h: i32 = window + .hwnd() + .ok() + .and_then(|h| win::workarea_for_hwnd(h.0)) + .map(|wa| wa.height()) + .or_else(|| { + window + .current_monitor() + .ok() + .flatten() + .map(|m| m.size().height as i32) + }) + .unwrap_or(1080); + let max_h = (wa_h as f64 * 0.95) as u32; + let min_w = (216.0 * scale).round() as u32; + let mut w = actual.width; + let mut h = w * 16 / 9; + if h > max_h { + h = max_h; + w = h * 9 / 16; + } + if w < min_w { + w = min_w; + h = w * 16 / 9; + if h > max_h { + h = max_h; + w = h * 9 / 16; + } + } + let tol = (RATIO_TOL * scale).ceil() as i64; + if (actual.width as i64 - w as i64).abs() > tol || (actual.height as i64 - h as i64).abs() > tol { + let _ = window.set_size(PhysicalSize::new(w, h)); + } +} + +// ============================================================================ +// 吸附 / 恢复 / 解除 +// ============================================================================ + +/// 收起 (W-SNP-01/02): 主窗口贴边隐藏, 悬浮球出现在边缘。 +/// edge 由 record_main_move 的触碰判定给出, 不在此重判。 +fn collapse(app: &AppHandle, edge: Edge) { + let Some(main) = app.get_webview_window(MAIN) else { return }; + let Ok(hwnd) = main.hwnd() else { return }; + let Some(wa) = win::workarea_for_hwnd(hwnd.0) else { return }; + let Ok(scale) = main.scale_factor() else { return }; + let (Ok(pos), Ok(size)) = (main.outer_position(), main.outer_size()) else { return }; + let rect = rect_of(pos, size); + let docked_pos = flush_pos(&rect, edge, &wa); + let offset = clamp_offset( + along_center(&rect, edge), + edge, + &wa, + HANDLE_LONG, + scale, + ); + { + let Some(st) = state(app) else { return }; + let mut s = st.lock().expect("shell state poisoned"); + s.docked = true; + s.mouse_outside_since = None; + s.cfg.dock = DockCfg { edge, offset }; + s.docked_rect = Some((docked_pos, size)); + let cfg = s.cfg.clone(); + let dir = s.data_dir.clone(); + drop(s); + cfg.save(&dir); + } + let _ = main.set_position(PhysicalPosition::new(docked_pos.x, docked_pos.y)); + let _ = main.hide(); + place_handle(app, edge, offset, &wa, scale); +} + +/// 悬浮球定位 + 显示 (W-SNP-02/03) +fn place_handle(app: &AppHandle, edge: Edge, offset: i32, wa: &win::WorkArea, scale: f64) { + let Some(dock) = app.get_webview_window(DOCK) else { return }; + let (lw, lh) = match edge { + Edge::Left | Edge::Right => (HANDLE_SHORT, HANDLE_LONG), + Edge::Top | Edge::Bottom => (HANDLE_LONG, HANDLE_SHORT), + }; + let w = (lw * scale).round() as i32; + let h = (lh * scale).round() as i32; + let (x, y) = match edge { + Edge::Left => (wa.left, wa.clamp_y(offset - h / 2, h)), + Edge::Right => (wa.right - w, wa.clamp_y(offset - h / 2, h)), + Edge::Top => (wa.clamp_x(offset - w / 2, w), wa.top), + Edge::Bottom => (wa.clamp_x(offset - w / 2, w), wa.bottom - h), + }; + let dark = state(app) + .map(|s| s.lock().expect("shell state poisoned").theme_dark) + .unwrap_or(false); + let _ = dock.set_size(PhysicalSize::new(w as u32, h as u32)); + let _ = dock.set_position(PhysicalPosition::new(x, y)); + let _ = dock.eval(&format!( + "window.__SCALE = {scale}; window.applyDock('{}', '{}')", + edge.css(), + if dark { "dark" } else { "light" } + )); + let _ = dock.show(); + if let Some(st) = state(app) { + st.lock().expect("shell state poisoned").dock_visible = true; + } +} + +/// 唤回主窗口 (W-SNP-04/05, W-EX-01): 恢复到吸附位置, 悬浮球消失 +fn restore_main(app: &AppHandle) { + let Some(st) = state(app) else { return }; + let docked_rect = { + let mut s = st.lock().expect("shell state poisoned"); + if !s.docked { + return; + } + s.dock_visible = false; + s.hover_since = None; + s.edge_armed = false; // 恢复位置贴边, 需先拖离才允许再次触碰收起 + s.last_main_rect = None; + s.mouse_outside_since = None; + // auto-hide 武装: 恢复时光标是否已在窗口区域内 + // (悬浮球唤回 = 在; 托盘/单实例唤回 = 光标在远处, 不武装, 否则立刻缩回) + s.mouse_inside_since_restore = win::cursor_pos().map_or(false, |(cx, cy)| { + s.docked_rect.map_or(false, |(p, sz)| { + cx >= p.x + && cx <= p.x + sz.width as i32 + && cy >= p.y + && cy <= p.y + sz.height as i32 + }) + }); + s.ignore_until = Some(Instant::now() + MOVE_SUPPRESS); + s.docked_rect + }; + let Some((pos, _size)) = docked_rect else { return }; + let Some(main) = app.get_webview_window(MAIN) else { return }; + let Some(dock) = app.get_webview_window(DOCK) else { return }; + let _ = dock.hide(); + if let Ok(hwnd) = main.hwnd() { + if let Some(wa) = win::workarea_for_hwnd(hwnd.0) { + if let Ok(size) = main.outer_size() { + let x = wa.clamp_x(pos.x, size.width as i32); + let y = wa.clamp_y(pos.y, size.height as i32); + let _ = main.set_position(PhysicalPosition::new(x, y)); + } + } + } + let _ = main.unminimize(); + let _ = main.show(); + let _ = main.set_focus(); +} + +/// 解除吸附 (W-SNP-05/14): drop = 拖出时的释放点 (悬浮球中心, 物理像素) +fn undock(app: &AppHandle, drop: Option<(i32, i32)>) { + let Some(st) = state(app) else { return }; + { + let mut s = st.lock().expect("shell state poisoned"); + s.docked = false; + s.dock_visible = false; + s.hover_since = None; + s.edge_armed = true; // 自由窗口, 触碰边缘可再次收起 + s.last_main_rect = None; + s.ignore_until = Some(Instant::now() + MOVE_SUPPRESS); + } + if let Some(dock) = app.get_webview_window(DOCK) { + let _ = dock.hide(); + } + let Some(main) = app.get_webview_window(MAIN) else { return }; + if let Some((cx, cy)) = drop { + // 主窗口以释放点为中心, 完整夹取进可工作区 (W-SNP-14) + let Ok(size) = main.outer_size() else { return }; + if let Ok(hwnd) = main.hwnd() { + if let Some(wa) = win::workarea_for_hwnd(hwnd.0) { + let x = wa.clamp_x(cx - size.width as i32 / 2, size.width as i32); + let y = wa.clamp_y(cy - size.height as i32 / 2, size.height as i32); + let _ = main.set_position(PhysicalPosition::new(x, y)); + } + } + let _ = main.show(); + let _ = main.set_focus(); + } +} + +// ============================================================================ +// 托盘 / 菜单 / 退出 +// ============================================================================ + +fn on_menu_event(app: &AppHandle, event: MenuEvent) { + match event.id().as_ref() { + "show" => activate_primary(app), + "quit" => quit(app), + "autostart" => toggle_autostart(app), + "snap" => toggle_snap(app), + _ => {} + } +} + +fn on_tray_event(tray: &tauri::tray::TrayIcon, event: TrayIconEvent) { + match event { + // 单击与双击均唤回主窗口, 幂等 (W-TRY-03/04) + TrayIconEvent::Click { button: MouseButton::Left, button_state: MouseButtonState::Up, .. } => { + activate_primary(tray.app_handle()); + } + TrayIconEvent::DoubleClick { button: MouseButton::Left, .. } => { + activate_primary(tray.app_handle()); + } + _ => {} + } +} + +/// 唤回入口统一处理: 收起态 → 恢复到吸附位置 (W-EX-01); 否则前置主窗口 +pub fn activate_primary(app: &AppHandle) { + let docked = state(app) + .map(|s| s.lock().expect("shell state poisoned").docked) + .unwrap_or(false); + if docked { + restore_main(app); + return; + } + if let Some(main) = app.get_webview_window(MAIN) { + let _ = main.unminimize(); + let _ = main.show(); + let _ = main.set_focus(); + } +} + +fn toggle_autostart(app: &AppHandle) { + use tauri_plugin_autostart::ManagerExt; + let on = app.autolaunch().is_enabled().unwrap_or(false); + let result = if on { + app.autolaunch().disable() + } else { + app.autolaunch().enable() + }; + // 写入失败时如实回显 (W-EX-06), 不静默假成功 + let new_on = if on { false } else { result.is_ok() }; + if let Some(st) = state(app) { + let s = st.lock().expect("shell state poisoned"); + if let Some(item) = &s.menu_autostart { + let _ = item.set_checked(new_on); + } + } +} + +fn toggle_snap(app: &AppHandle) { + let Some(st) = state(app) else { return }; + let (on, cfg, dir) = { + let mut s = st.lock().expect("shell state poisoned"); + s.cfg.snap_enabled = !s.cfg.snap_enabled; + if let Some(item) = &s.menu_snap { + let _ = item.set_checked(s.cfg.snap_enabled); + } + (s.cfg.snap_enabled, s.cfg.clone(), s.data_dir.clone()) + }; + let _ = on; + cfg.save(&dir); +} + +/// 唯一正常退出入口 (W-TRY-06 / W-EX-02): 先收 UI, 再保存配置, 最后退出 +pub fn quit(app: &AppHandle) { + if let Some(dock) = app.get_webview_window(DOCK) { + let _ = dock.hide(); + } + if let Some(main) = app.get_webview_window(MAIN) { + save_window_cfg(app); + let _ = main.hide(); + } + if let Some(st) = state(app) { + let s = st.lock().expect("shell state poisoned"); + s.cfg.save(&s.data_dir); + } + let _ = app.remove_tray_by_id(TRAY_ID); + app.exit(0); +} + +// ============================================================================ +// 悬浮球 IPC (dock.html → Rust) +// ============================================================================ + +pub fn dock_hover_start(app: &AppHandle) { + if let Some(st) = state(app) { + st.lock().expect("shell state poisoned").hover_since = Some(Instant::now()); + } +} + +pub fn dock_hover_end(app: &AppHandle) { + if let Some(st) = state(app) { + st.lock().expect("shell state poisoned").hover_since = None; + } +} + +/// 单击/触屏点按 → 立即恢复 (W-SNP-05) +pub fn dock_click(app: &AppHandle) { + let docked = state(app) + .map(|s| s.lock().expect("shell state poisoned").docked) + .unwrap_or(false); + if docked { + restore_main(app); + } +} + +pub fn dock_drag_start(app: &AppHandle) { + if let Some(st) = state(app) { + let mut s = st.lock().expect("shell state poisoned"); + s.dragging = true; + s.hover_since = None; // 拖动取消未决的 hover 恢复 + } +} + +pub fn dock_drag_move(app: &AppHandle, dx: i32, dy: i32) { + let Some(dock) = app.get_webview_window(DOCK) else { return }; + if let Ok(pos) = dock.outer_position() { + let _ = dock.set_position(PhysicalPosition::new(pos.x + dx, pos.y + dy)); + } +} + +/// 拖动结束: ≤8px 停靠该边 (含换边 W-SNP-13); 8~32px 吸附到最近边; ≥32px 拖出恢复 (W-SNP-14) +pub fn dock_drag_end(app: &AppHandle) { + { + let Some(st) = state(app) else { return }; + st.lock().expect("shell state poisoned").dragging = false; + } + let Some(dock) = app.get_webview_window(DOCK) else { return }; + let (Ok(pos), Ok(size)) = (dock.outer_position(), dock.outer_size()) else { return }; + let Ok(hwnd) = dock.hwnd() else { return }; + let Some(wa) = win::workarea_for_hwnd(hwnd.0) else { return }; + let Ok(scale) = dock.scale_factor() else { return }; + let rect = rect_of(pos, size); + let (edge, dist) = nearest_edge(&rect, &wa); + if dist >= UNDOCK_PX { + let center = (rect.left + rect.width() / 2, rect.top + rect.height() / 2); + undock(app, Some(center)); + return; + } + // 停靠 (含 8~32px 死区: 吸附到最近边, 悬浮球不悬空) + let offset = clamp_offset(along_center(&rect, edge), edge, &wa, HANDLE_LONG, scale); + { + let Some(st) = state(app) else { return }; + let mut s = st.lock().expect("shell state poisoned"); + s.docked = true; + s.mouse_outside_since = None; + s.cfg.dock = DockCfg { edge, offset }; + // 主窗口恢复位置改为新边的贴边矩形 (W-SNP-13: 下次恢复出现在新边缘) + let main_size = s + .docked_rect + .map(|(_, sz)| sz) + .or_else(|| app.get_webview_window(MAIN).and_then(|m| m.outer_size().ok())); + if let Some(msize) = main_size { + let (x, y) = main_flush_from_offset(edge, offset, msize, &wa); + s.docked_rect = Some((PhysicalPosition::new(x, y), msize)); + } + let cfg = s.cfg.clone(); + let dir = s.data_dir.clone(); + drop(s); + cfg.save(&dir); + } + place_handle(app, edge, offset, &wa, scale); +} + +// ============================================================================ +// 全屏避让 / 主题跟随 +// ============================================================================ + +/// 独占全屏 → 隐藏悬浮球; 退出 → 原位恢复 (W-SNP-16)。无边框全屏不受影响 (W-SNP-15)。 +fn fullscreen_check(app: &AppHandle) { + let exclusive = win::is_exclusive_fullscreen(); + let Some(st) = state(app) else { return }; + let (transition, was_visible) = { + let mut s = st.lock().expect("shell state poisoned"); + let mut t = None; + if exclusive && s.dock_visible && !s.fs_hidden { + s.fs_hidden = true; + t = Some(false); + } else if !exclusive && s.fs_hidden { + s.fs_hidden = false; + t = Some(true); + } + (t, s.dock_visible) + }; + match transition { + Some(false) => { + if let Some(d) = app.get_webview_window(DOCK) { + let _ = d.hide(); + } + } + Some(true) => { + if was_visible { + if let Some(d) = app.get_webview_window(DOCK) { + let _ = d.show(); + } + } + } + None => {} + } +} + +/// 吸附态已展开 (悬浮球唤回) 时, 光标移开窗口 → 自动收起 (auto-hide 停靠交互)。 +/// 仅当光标在展开后进过窗口才武装 —— 托盘/单实例唤回时光标在远处, 不缩回。 +fn autohide_check(app: &AppHandle) { + let now = Instant::now(); + let armed = { + let Some(st) = state(app) else { return }; + let s = st.lock().expect("shell state poisoned"); + if !s.docked || s.dock_visible || s.dragging { + return; + } + if s.ignore_until.map_or(false, |u| now < u) { + return; + } + // 窗口正在移动 (拖拽中) 时不判定, 避免拖拽途中误收 + if s.last_main_move.map_or(false, |t| now.duration_since(t) < MOVE_QUIET * 2) { + return; + } + s.mouse_inside_since_restore + }; + if !armed { + return; + } + let Some(main) = app.get_webview_window(MAIN) else { return }; + let (Ok(pos), Ok(size)) = (main.outer_position(), main.outer_size()) else { return }; + let Some((cx, cy)) = win::cursor_pos() else { return }; + let inside = cx >= pos.x + && cx <= pos.x + size.width as i32 + && cy >= pos.y + && cy <= pos.y + size.height as i32; + let outside_since = { + let Some(st) = state(app) else { return }; + let mut s = st.lock().expect("shell state poisoned"); + if inside { + s.mouse_outside_since = None; + None + } else { + Some(*s.mouse_outside_since.get_or_insert(now)) + } + }; + if let Some(t) = outside_since { + if now.duration_since(t) >= AUTOHIDE_DELAY { + let edge = { + let Some(st) = state(app) else { return }; + st.lock().expect("shell state poisoned").cfg.dock.edge + }; + collapse(app, edge); + } + } +} + +/// 深浅色跟随系统 (W-THE): 托盘图标 + 悬浮球外观 +fn theme_check(app: &AppHandle) { + let dark = win::system_prefers_dark(); + let changed = { + let Some(st) = state(app) else { return }; + let mut s = st.lock().expect("shell state poisoned"); + if s.theme_dark == dark { + false + } else { + s.theme_dark = dark; + true + } + }; + if !changed { + return; + } + if let Ok(icon) = tray_icon_image(dark) { + if let Some(tray) = app.tray_by_id(TRAY_ID) { + let _ = tray.set_icon(Some(icon)); + } + } + if let Some(d) = app.get_webview_window(DOCK) { + let _ = d.eval(&format!("window.applyTheme('{}')", if dark { "dark" } else { "light" })); + } +} + +// ============================================================================ +// 配置持久化 +// ============================================================================ + +/// 启动时应用记忆的窗口尺寸/位置 (W-WIN-07) +/// 尺寸只取宽度, 高度按 405:720 锁定比例推导 (见 config::height_for_width) +fn apply_saved_window(main: &tauri::WebviewWindow, cfg: &ShellConfig) { + let Ok(scale) = main.scale_factor() else { return }; + let w = cfg.window.width.clamp(config::WIDTH_MIN, config::WIDTH_MAX); + let h = config::height_for_width(w); + let _ = main.set_size(tauri::LogicalSize::new(w, h)); + if let (Some(x), Some(y)) = (cfg.window.x, cfg.window.y) { + let pw = (w * scale).round() as i32; + let ph = (h * scale).round() as i32; + if let Some(wa) = win::workarea_for_point(x + pw / 2, y + ph / 2) { + let nx = wa.clamp_x(x, pw); + let ny = wa.clamp_y(y, ph); + let _ = main.set_position(PhysicalPosition::new(nx, ny)); + } + // 无效位置 → 保持配置默认居中 (W-WIN-07) + } +} + +/// 关闭/退出时保存窗口尺寸与位置 (W-WIN-07) +fn save_window_cfg(app: &AppHandle) { + let Some(main) = app.get_webview_window(MAIN) else { return }; + let Some(st) = state(app) else { return }; + let Ok(scale) = main.scale_factor() else { return }; + let (Ok(size), Ok(pos)) = (main.outer_size(), main.outer_position()) else { return }; + let cfg = { + let mut s = st.lock().expect("shell state poisoned"); + // 只记宽度; 高度按锁定比例推导, 保证下次启动比例不变 + s.cfg.window.width = size.width as f64 / scale; + s.cfg.window.normalize(); + s.cfg.window_width = Some(s.cfg.window.width); + s.cfg.window.x = Some(pos.x); + s.cfg.window.y = Some(pos.y); + s.cfg.clone() + }; + let dir = st.lock().expect("shell state poisoned").data_dir.clone(); + cfg.save(&dir); +} + +// ============================================================================ +// 几何辅助 +// ============================================================================ + +#[derive(Clone, Copy)] +struct Rect { + left: i32, + top: i32, + right: i32, + bottom: i32, +} + +impl Rect { + fn width(&self) -> i32 { + self.right - self.left + } + fn height(&self) -> i32 { + self.bottom - self.top + } +} + +fn rect_of(pos: PhysicalPosition, size: PhysicalSize) -> Rect { + Rect { + left: pos.x, + top: pos.y, + right: pos.x + size.width as i32, + bottom: pos.y + size.height as i32, + } +} + +/// 最近边与距离 (窗口边缘 ↔ 可工作区边缘) +fn nearest_edge(rect: &Rect, wa: &win::WorkArea) -> (Edge, f64) { + let candidates = [ + (Edge::Left, (rect.left - wa.left).abs()), + (Edge::Right, (wa.right - rect.right).abs()), + (Edge::Top, (rect.top - wa.top).abs()), + (Edge::Bottom, (wa.bottom - rect.bottom).abs()), + ]; + let (edge, dist) = candidates + .into_iter() + .min_by_key(|(_, d)| *d) + .unwrap_or((Edge::Left, i32::MAX)); + (edge, dist as f64) +} + +/// 触碰判定 (W-SNP-01, 修订决策 #1): +/// ① 入带 —— 当前窗口边缘落在边缘条带 [edge, edge+SNAP] 内; +/// ② 越线 —— 上一样本在带外, 当前样本已到线或越过线 (快速拖动跨过条带的采样间隙)。 +/// 对不可见边框 (视觉贴边时 rect 已越界 7~9px) 与过冲免疫; +/// 停泊在边缘以外 (如窗口压住任务栏区, rect.bottom > wa.bottom) 不算触碰。 +fn edge_touching(prev: Option<&Rect>, cur: &Rect, wa: &win::WorkArea) -> Option { + let s = SNAP_PX as i32; + let in_band = |a: i32, lo: i32, hi: i32| a >= lo && a <= hi; + let crossed = |p: i32, c: i32, line: i32, outward: bool| { + if outward { + // Bottom: 带在 [line-8, line], "带外"指 p < line-8 + p < line - s && c >= line - s + } else { + // Left/Right/Top: 带在 [line, line+8], "带外"指 p > line+8 + p > line + s && c <= line + s + } + }; + let hit = + |p: Option, c: i32, lo: i32, hi: i32, line: i32, outward: bool| match p { + Some(p) => in_band(c, lo, hi) || crossed(p, c, line, outward), + None => in_band(c, lo, hi), + }; + if hit(prev.map(|r| r.left), cur.left, wa.left, wa.left + s, wa.left, false) { + Some(Edge::Left) + } else if hit(prev.map(|r| r.right), cur.right, wa.right - s, wa.right, wa.right, true) { + Some(Edge::Right) + } else if hit(prev.map(|r| r.top), cur.top, wa.top, wa.top + s, wa.top, false) { + Some(Edge::Top) + } else if hit(prev.map(|r| r.bottom), cur.bottom, wa.bottom - s, wa.bottom, wa.bottom, true) { + Some(Edge::Bottom) + } else { + None + } +} + +/// 贴边位置: 保持沿边坐标, 垂直于边的坐标贴到边缘 +fn flush_pos(rect: &Rect, edge: Edge, wa: &win::WorkArea) -> PhysicalPosition { + match edge { + Edge::Left => PhysicalPosition::new(wa.left, wa.clamp_y(rect.top, rect.height())), + Edge::Right => PhysicalPosition::new(wa.right - rect.width(), wa.clamp_y(rect.top, rect.height())), + Edge::Top => PhysicalPosition::new(wa.clamp_x(rect.left, rect.width()), wa.top), + Edge::Bottom => PhysicalPosition::new(wa.clamp_x(rect.left, rect.width()), wa.bottom - rect.height()), + } +} + +/// 主窗口在给定边/沿边偏移下的贴边位置 +fn main_flush_from_offset( + edge: Edge, + offset: i32, + size: PhysicalSize, + wa: &win::WorkArea, +) -> (i32, i32) { + let w = size.width as i32; + let h = size.height as i32; + match edge { + Edge::Left => (wa.left, wa.clamp_y(offset - h / 2, h)), + Edge::Right => (wa.right - w, wa.clamp_y(offset - h / 2, h)), + Edge::Top => (wa.clamp_x(offset - w / 2, w), wa.top), + Edge::Bottom => (wa.clamp_x(offset - w / 2, w), wa.bottom - h), + } +} + +/// 沿边中心 (Left/Right → 垂直中心; Top/Bottom → 水平中心) +fn along_center(rect: &Rect, edge: Edge) -> i32 { + match edge { + Edge::Left | Edge::Right => rect.top + rect.height() / 2, + Edge::Top | Edge::Bottom => rect.left + rect.width() / 2, + } +} + +/// 悬浮球沿边位置夹取 (完整可见, W-SNP-02) +fn clamp_offset(offset: i32, edge: Edge, wa: &win::WorkArea, handle_long: f64, scale: f64) -> i32 { + let half = (handle_long * scale / 2.0).round() as i32; + match edge { + Edge::Left | Edge::Right => offset.clamp(wa.top + half, wa.bottom - half), + Edge::Top | Edge::Bottom => offset.clamp(wa.left + half, wa.right - half), + } +} + +fn tray_icon_image(dark: bool) -> tauri::Result> { + if dark { + tauri::image::Image::from_bytes(include_bytes!("../../icons/tray-dark.png")) + } else { + tauri::image::Image::from_bytes(include_bytes!("../../icons/tray-light.png")) + } +} diff --git a/src-tauri/src/shell/win.rs b/src-tauri/src/shell/win.rs new file mode 100644 index 0000000..1bc1506 --- /dev/null +++ b/src-tauri/src/shell/win.rs @@ -0,0 +1,117 @@ +//! Win32 辅助: 可工作区 / 独占全屏检测 / 系统深浅色 (W-SNP、W-THE) +#![cfg(windows)] + +use windows_sys::Win32::{ + Foundation::{HWND, POINT, RECT}, + Graphics::Gdi::{ + GetMonitorInfoW, MonitorFromPoint, MonitorFromWindow, MONITORINFO, MONITOR_DEFAULTTONEAREST, + }, + System::Registry::{RegGetValueW, HKEY_CURRENT_USER, RRF_RT_REG_DWORD}, + UI::Shell::{SHQueryUserNotificationState, QUNS_RUNNING_D3D_FULL_SCREEN}, + UI::WindowsAndMessaging::GetCursorPos, +}; + +/// 当前光标位置 (物理像素) +pub fn cursor_pos() -> Option<(i32, i32)> { + let mut pt = POINT { x: 0, y: 0 }; + unsafe { + if GetCursorPos(&mut pt) == 0 { + return None; + } + } + Some((pt.x, pt.y)) +} + +/// 显示器可工作区 (物理像素, 已扣除任务栏, W-EX-05) +#[derive(Debug, Clone, Copy)] +pub struct WorkArea { + pub left: i32, + pub top: i32, + pub right: i32, + pub bottom: i32, +} + +impl WorkArea { + pub fn height(&self) -> i32 { + self.bottom - self.top + } + pub fn clamp_x(&self, x: i32, w: i32) -> i32 { + x.clamp(self.left, (self.right - w).max(self.left)) + } + pub fn clamp_y(&self, y: i32, h: i32) -> i32 { + y.clamp(self.top, (self.bottom - h).max(self.top)) + } +} + +fn utf16(s: &str) -> Vec { + s.encode_utf16().chain(std::iter::once(0)).collect() +} + +/// 窗口所在显示器的可工作区 (hwnd: tauri `hwnd().0`, 即裸指针) +pub fn workarea_for_hwnd(hwnd: *mut std::ffi::c_void) -> Option { + unsafe { + let hmon = MonitorFromWindow(hwnd as HWND, MONITOR_DEFAULTTONEAREST); + if hmon.is_null() { + return None; + } + let mut info: MONITORINFO = std::mem::zeroed(); + info.cbSize = std::mem::size_of::() as u32; + if GetMonitorInfoW(hmon, &mut info) == 0 { + return None; + } + let RECT { left, top, right, bottom } = info.rcWork; + Some(WorkArea { left, top, right, bottom }) + } +} + +/// 前台是否为独占全屏 (D3D FSE, W-SNP-16)。 +/// 无边框全屏 / 全屏优化不在此列 (悬浮球保持可见, W-SNP-15)。 +pub fn is_exclusive_fullscreen() -> bool { + unsafe { + let mut state: i32 = 0; + if SHQueryUserNotificationState(&mut state) < 0 { + return false; // 判定失败 → 默认非全屏 (W-EX-11) + } + state == QUNS_RUNNING_D3D_FULL_SCREEN + } +} + +/// 系统「应用模式」是否为深色 (W-THE-01, AppsUseLightTheme == 0) +pub fn system_prefers_dark() -> bool { + unsafe { + let subkey = utf16("Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"); + let value = utf16("AppsUseLightTheme"); + let mut val: u32 = 1; // 缺省按浅色 + let mut size = std::mem::size_of::() as u32; + let status = RegGetValueW( + HKEY_CURRENT_USER, + subkey.as_ptr(), + value.as_ptr(), + RRF_RT_REG_DWORD, + std::ptr::null_mut(), + (&mut val as *mut u32).cast(), + &mut size, + ); + if status != 0 { + return false; // 读不到按浅色 (W-EX-11 口径) + } + val == 0 + } +} + +/// 物理坐标所在显示器的可工作区 (启动位置校验, W-WIN-07) +pub fn workarea_for_point(x: i32, y: i32) -> Option { + unsafe { + let hmon = MonitorFromPoint(POINT { x, y }, MONITOR_DEFAULTTONEAREST); + if hmon.is_null() { + return None; + } + let mut info: MONITORINFO = std::mem::zeroed(); + info.cbSize = std::mem::size_of::() as u32; + if GetMonitorInfoW(hmon, &mut info) == 0 { + return None; + } + let RECT { left, top, right, bottom } = info.rcWork; + Some(WorkArea { left, top, right, bottom }) + } +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index cc20d44..33d518a 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,36 +1,38 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "iBoard", - "version": "0.1.0", + "version": "0.2.1", "identifier": "com.iboard.app", "build": { "beforeDevCommand": "npm run dev", - "beforeBuildCommand": "npm run build", + "beforeBuildCommand": "npm run build:shell", "devUrl": "http://localhost:5173", "frontendDist": "../dist" }, "plugins": { "hotswap": { - "endpoint": "http://bh.vps.honor3.com:3001/api/ota/check/{{current_sequence}}", + "endpoint": "https://iboard.honor3.com/api/ota/check/{{current_sequence}}", "pubkey": "RWQQ4/wfHjJiC/O6NmA7eIQYUNRpCsz5wLLsLqJXgHmsUraAavUWx0HA", "require_https": false, "headers": {} } }, "app": { + "withGlobalTauri": true, "windows": [ { "title": "iBoard", - "width": 420, - "height": 820, - "minWidth": 360, - "minHeight": 640, + "width": 323, + "height": 574, + "minWidth": 242, + "minHeight": 430, "resizable": true, + "visible": false, "fullscreen": false } ], "security": { - "csp": "default-src 'self' ipc: http://ipc.localhost; img-src 'self' data: blob: asset: http://asset.localhost; connect-src 'self' ipc: http://ipc.localhost http://bh.vps.honor3.com:3001 https://bh.vps.honor3.com:3001; style-src 'self' 'unsafe-inline'; script-src 'self'" + "csp": "default-src 'self' ipc: http://ipc.localhost; img-src 'self' data: blob: asset: http://asset.localhost; connect-src 'self' ipc: http://ipc.localhost https://iboard.honor3.com http://bh.vps.honor3.com:3001 https://bh.vps.honor3.com:3001; style-src 'self' 'unsafe-inline'; script-src 'self'" } }, "bundle": { @@ -43,6 +45,13 @@ "icons/icon.icns", "icons/icon.ico" ], + "windows": { + "nsis": { + "installMode": "currentUser", + "languages": ["SimpChinese", "English"], + "displayLanguageSelector": false + } + }, "android": { "minSdkVersion": 24 } diff --git a/src/App.jsx b/src/App.jsx index b76aede..068818e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,19 +1,23 @@ import { Routes, Route, Navigate, useLocation } from 'react-router-dom' import MobileShell from './layouts/MobileShell.jsx' import LoginPage from './pages/LoginPage.jsx' -import HomePage from './pages/HomePage.jsx' -import NewsDetail from './pages/news/NewsDetail.jsx' import MacroPage from './pages/MacroPage.jsx' import MonitorPage from './pages/MonitorPage.jsx' import ServerDetail from './pages/monitor/ServerDetail.jsx' import MePage from './pages/MePage.jsx' +import CheckinPage from './pages/checkin/CheckinPage.jsx' +import TaskPage from './pages/task/TaskPage.jsx' import { useAuth } from './auth/AuthContext.jsx' +import { PowerSyncProvider } from './lib/powersync/react.jsx' -// 已登录访问 /login 自动回首页 +// 默认落地页: 打卡 (V0.2 起打卡是第一个 Tab 且为默认选中) +const HOME_PATH = '/checkin' + +// 已登录访问 /login 自动回默认页 function RedirectIfAuthed({ children }) { const { user, loading } = useAuth() if (loading) return null - if (user) return + if (user) return return children } @@ -28,17 +32,26 @@ function RequireAuth({ children }) { export default function App() { return ( - + + } /> }> - } /> - } /> + {/* 打卡为默认首页 */} + } /> + } /> + {/* 任务模块 (V0.2): 首屏(最近项目+当前项目) / 项目列表 / 深链 (T-VW-08/09) */} + } /> + } /> + } /> + {/* 宏观: 报警 / 新闻 / 油价 / 经济数据 / 策略研究 / 经济文章 (V0.2 原首页并入, 默认新闻) */} + } /> } /> } /> } /> } /> - } /> - + } /> + + ) } \ No newline at end of file diff --git a/src/api/checkin.js b/src/api/checkin.js new file mode 100644 index 0000000..aca18c8 --- /dev/null +++ b/src/api/checkin.js @@ -0,0 +1,42 @@ +import { api } from './client.js' + +const q = (params) => { + const s = new URLSearchParams() + Object.entries(params || {}).forEach(([k, v]) => { + if (v !== undefined && v !== null && v !== '') s.set(k, v) + }) + const str = s.toString() + return str ? '?' + str : '' +} + +// 打卡 / 日历 +export const checkinApi = { + habits: (date) => api('/checkin/habits' + q({ date })), + day: (date) => api('/checkin/day' + q({ date })), + calendar: (month) => api('/checkin/calendar' + q({ month })), + createHabit: (body) => api('/checkin/habits', { method: 'POST', body }), + updateHabit: (id, body) => api(`/checkin/habits/${id}`, { method: 'PUT', body }), + reorderHabits: (ids) => api('/checkin/habits/reorder', { method: 'POST', body: { ids } }), + deleteHabit: (id, hard) => api(`/checkin/habits/${id}${hard ? '?hard=1' : ''}`, { method: 'DELETE' }), + check: (id, date, note) => api(`/checkin/habits/${id}/check`, { method: 'POST', body: { date, note } }), + uncheck: (id, date) => api(`/checkin/habits/${id}/check` + q({ date }), { method: 'DELETE' }), +} + +// 检查清单 +export const checklistApi = { + lists: (archived) => api('/checklists' + q({ archived: archived ? 1 : 0 })), + list: (id) => api(`/checklists/${id}`), + create: (body) => api('/checklists', { method: 'POST', body }), + update: (id, body) => api(`/checklists/${id}`, { method: 'PUT', body }), + remove: (id) => api(`/checklists/${id}`, { method: 'DELETE' }), + duplicate: (id) => api(`/checklists/${id}/duplicate`, { method: 'POST', body: {} }), + addItem: (id, body) => api(`/checklists/${id}/items`, { method: 'POST', body }), + updateItem: (itemId, body) => api(`/checklists/items/${itemId}`, { method: 'PUT', body }), + removeItem: (itemId) => api(`/checklists/items/${itemId}`, { method: 'DELETE' }), + checkAll: (id, done) => api(`/checklists/${id}/check-all`, { method: 'POST', body: { done } }), + templates: () => api('/checklists/templates'), + template: (id) => api(`/checklists/templates/${id}`), + createTemplate: (body) => api('/checklists/templates', { method: 'POST', body }), + updateTemplate: (id, body) => api(`/checklists/templates/${id}`, { method: 'PUT', body }), + removeTemplate: (id) => api(`/checklists/templates/${id}`, { method: 'DELETE' }), +} diff --git a/src/api/client.js b/src/api/client.js index f1e0a63..560ef43 100644 --- a/src/api/client.js +++ b/src/api/client.js @@ -1,13 +1,121 @@ // API 调用封装: 自动带 JWT、401 自动清 token、抛统一错误 +// 0.2.1 离线支持: +// - GET 请求成功后把响应写入本地缓存 (localStorage), 离线/网络失败时回退缓存 (界面仍有数据) +// - 写请求 (POST/PUT/DELETE) 仍要求在线 (任务模块的离线写走 PowerSync 上传队列) +// - 统一在线/离线状态订阅, 供 UI 显示离线标识 const TOKEN_KEY = 'iboard_token' const REMEMBER_KEY = 'iboard_remember' +const UID_KEY = 'iboard_uid' // API 基址: // - 默认相对路径 `/api` (web dev: Vite 代理; web prod: 同源 Express 一起托管) -// - 打包 Android 时设 VITE_API_BASE=http://bh.vps.honor3.com:3001 走外网后端 +// - 打包 Android / Windows 外壳时设 VITE_API_BASE=http://bh.vps.honor3.com:3001 走外网后端 const API_BASE = (import.meta.env.VITE_API_BASE || '').replace(/\/+$/, '') const API_PREFIX = API_BASE ? API_BASE + '/api' : '/api' +// ---------- 在线/离线状态 (网络事件 + 请求结果联动) ---------- +let offline = typeof navigator !== 'undefined' ? navigator.onLine === false : false +const offlineListeners = new Set() + +export function isOffline() { + return offline +} + +function setOffline(next) { + if (offline === next) return + offline = next + offlineListeners.forEach((cb) => { + try { cb(next) } catch { /* ignore */ } + }) +} + +if (typeof window !== 'undefined') { + window.addEventListener('online', () => setOffline(false)) + window.addEventListener('offline', () => setOffline(true)) +} + +/** 订阅在线/离线变化, 返回取消函数 */ +export function subscribeOffline(cb) { + offlineListeners.add(cb) + return () => offlineListeners.delete(cb) +} + +// ---------- GET 响应本地缓存 ---------- +// 目的: 任何界面只要成功访问过一次, 离线时都能展示上次的数据 +const CACHE_PREFIX = 'iboard_cache:' +const CACHE_TOTAL_LIMIT = 3 * 1024 * 1024 // 约 3MB (localStorage 通常 5MB) +const CACHE_ENTRY_LIMIT = 512 * 1024 // 单条上限, 超大响应不缓存 + +function cacheKey(path) { + return CACHE_PREFIX + path +} + +function readCache(path) { + try { + const raw = localStorage.getItem(cacheKey(path)) + if (!raw) return null + const parsed = JSON.parse(raw) + if (!parsed || typeof parsed !== 'object' || !('data' in parsed)) return null + return parsed + } catch { + return null + } +} + +function cacheSizeUsed() { + let total = 0 + try { + for (let i = 0; i < localStorage.length; i++) { + const k = localStorage.key(i) + if (k && k.startsWith(CACHE_PREFIX)) total += (localStorage.getItem(k) || '').length + } + } catch { /* ignore */ } + return total +} + +function evictOldest(needBytes) { + try { + const entries = [] + for (let i = 0; i < localStorage.length; i++) { + const k = localStorage.key(i) + if (!k || !k.startsWith(CACHE_PREFIX)) continue + let ts = 0 + try { ts = JSON.parse(localStorage.getItem(k) || '{}').ts || 0 } catch { /* ignore */ } + entries.push({ k, ts }) + } + entries.sort((a, b) => a.ts - b.ts) + let used = cacheSizeUsed() + for (const e of entries) { + if (used + needBytes <= CACHE_TOTAL_LIMIT) break + used -= (localStorage.getItem(e.k) || '').length + localStorage.removeItem(e.k) + } + } catch { /* ignore */ } +} + +function writeCache(path, data) { + try { + const payload = JSON.stringify({ ts: Date.now(), data }) + if (payload.length > CACHE_ENTRY_LIMIT) return // 过大不缓存 + evictOldest(payload.length) + localStorage.setItem(cacheKey(path), payload) + } catch { + // 配额满等异常: 静默失败, 不影响主流程 + } +} + +/** 清空所有 GET 缓存 (退出登录时调用) */ +export function clearApiCache() { + try { + const keys = [] + for (let i = 0; i < localStorage.length; i++) { + const k = localStorage.key(i) + if (k && k.startsWith(CACHE_PREFIX)) keys.push(k) + } + keys.forEach((k) => localStorage.removeItem(k)) + } catch { /* ignore */ } +} + export function getToken() { return localStorage.getItem(TOKEN_KEY) || '' } @@ -26,21 +134,45 @@ export function setRememberFlag(v) { else localStorage.removeItem(REMEMBER_KEY) } +// 当前用户 id (0.2.1 离线写入需要 ownerId/creatorId, 本地库无法即时从服务端获取) +export function getCurrentUserId() { + return localStorage.getItem(UID_KEY) || '' +} +export function setCurrentUserId(id) { + if (id !== null && id !== undefined && id !== '') localStorage.setItem(UID_KEY, String(id)) + else localStorage.removeItem(UID_KEY) +} + export function getApiBase() { return API_BASE } export async function api(path, { method = 'GET', body, headers = {} } = {}) { const token = getToken() - const res = await fetch(API_PREFIX + path, { - method, - headers: { - 'Content-Type': 'application/json', - ...(token ? { Authorization: 'Bearer ' + token } : {}), - ...headers, - }, - body: body ? JSON.stringify(body) : undefined, - }) + const isRead = method === 'GET' + let res + try { + res = await fetch(API_PREFIX + path, { + method, + headers: { + 'Content-Type': 'application/json', + ...(token ? { Authorization: 'Bearer ' + token } : {}), + ...headers, + }, + body: body ? JSON.stringify(body) : undefined, + }) + } catch { + // 网络层失败: 离线 / 服务不可达 + setOffline(true) + if (isRead) { + const cached = readCache(path) + if (cached) return cached.data + throw new Error('当前离线,且该界面尚无本地缓存') + } + throw new Error('当前离线,修改操作需要联网后重试') + } + + setOffline(false) if (res.status === 401) { setToken('') const data = await res.json().catch(() => ({})) @@ -49,5 +181,6 @@ export async function api(path, { method = 'GET', body, headers = {} } = {}) { } const data = await res.json().catch(() => ({})) if (!res.ok) throw new Error(data.error || '请求失败 (' + res.status + ')') + if (isRead) writeCache(path, data) return data -} \ No newline at end of file +} diff --git a/src/api/ota.js b/src/api/ota.js index a6d2601..2a36de4 100644 --- a/src/api/ota.js +++ b/src/api/ota.js @@ -1,177 +1,278 @@ -// 前端 OTA 封装 -// - Tauri 客户端: 直接用 tauri-plugin-hotswap-api (checkUpdate / applyUpdate / ...) -// - Web 浏览器: 只走我们自己的 /api/ota/manifest, 提示用户手动刷新 (Vite 资源指纹会失效) -// -// 设计原则: -// - Web 端只能"查 + 提示", 不能真下载应用, 避免误导 -// - Tauri 端走 hotswap 插件的官方 API, 签名校验/回滚/资源替换全交给插件 -import { getApiBase, getToken } from './client.js' -import { APP_VERSION, BUILD_ID } from '../version.js' - -// 判断是否在 Tauri WebView 内 (有 __TAURI_INTERNALS__ 即 Tauri 环境) -const inTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window - -// 拼接绝对 URL: 在 Tauri (VITE_API_BASE 有值) 时直连, 否则同源 -function resolveUrl(p) { - const base = getApiBase() - return base ? base + p : p -} - -// 当前版本信息 (前端展示用) -export function getCurrentVersionInfo() { - return { version: APP_VERSION, build: BUILD_ID } -} - -// 启动时: 确认当前资源可用 (Tauri only) -// 失败/异常都不抛, 仅 console.warn -export function initOta() { - if (!inTauri) return - // 动态 import: web 模式根本不打这个 chunk - import('tauri-plugin-hotswap-api') - .then((mod) => mod.notifyReady()) - .then(() => console.log('[ota] notifyReady ok')) - .catch((e) => console.warn('[ota] notifyReady failed:', e)) -} - -// 检查更新 -// 返回统一结构, 调用方不关心是 Tauri 还是 web: -// { -// available: boolean, -// reason: 'no_package' | 'same_version' | 'ota_disabled' | 'unsupported' | 'ok', -// info: { version, sequence, notes, mandatory, bundle_size } -// } -export async function checkOtaUpdate() { - if (!inTauri) { - return checkOtaUpdateWeb() - } - return checkOtaUpdateTauri() -} - -async function checkOtaUpdateTauri() { - // 走 hotswap 插件 (内部会带 sequence / platform / arch 等参数) - const { checkUpdate } = await import('tauri-plugin-hotswap-api') - const r = await checkUpdate() - if (!r.available) { - return { available: false, reason: 'no_package', info: r } - } - return { - available: true, - reason: 'ok', - info: { - version: r.version, - sequence: r.sequence, - notes: r.notes, - mandatory: r.mandatory, - bundle_size: r.bundle_size, - }, - } -} - -async function checkOtaUpdateWeb() { - // Web 端不能下载, 只能拿到版本号/构建号做比对, 提示用户刷新 - try { - const res = await fetch(resolveUrl('/api/ota/manifest-web'), { method: 'GET' }) - if (!res.ok) { - return { available: false, reason: 'unsupported', info: null } - } - const data = await res.json() - if (!data.available) return { available: false, reason: 'no_package', info: data } - if (data.build_id && data.build_id === BUILD_ID) { - return { available: false, reason: 'same_version', info: data } - } - return { - available: true, - reason: 'ok', - info: { - version: data.version, - notes: data.note || '', - // web 端没有强制/mandatory 等字段, 仅展示 - mandatory: false, - bundle_size: data.size || 0, - }, - } - } catch (e) { - console.warn('[ota] web check failed:', e) - return { available: false, reason: 'unsupported', info: null } - } -} - -// 下载并应用更新 (Tauri only). 调用方负责 UI 提示和错误处理 -// onProgress(received, total) 进度回调 -// 成功后必须 reload, 新资源才会生效 -export async function applyOtaUpdate(onProgress) { - if (!inTauri) { - throw new Error('OTA 升级仅在 Tauri 客户端可用, Web 端请直接刷新页面') - } - const mod = await import('tauri-plugin-hotswap-api') - - // 注册一次性进度监听 (applyUpdate 期间触发) - let unlisten = null - let unlistenLifecycle = null - if (onProgress) { - try { - unlisten = await mod.onDownloadProgress((p) => onProgress(p.downloaded, p.total)) - } catch (e) { - console.warn('[ota] onDownloadProgress 监听失败:', e) - } - } - - // 监听 lifecycle 事件, 捕获失败详情 (download-error / apply 失败时 error 字段带具体原因) - // applyUpdate() 抛出的错误往往只有泛化信息, 细节藏在 lifecycle 事件里 - let lifecycleError = '' - try { - unlistenLifecycle = await mod.onLifecycle((ev) => { - if (ev.event === 'download-error' || ev.event === 'apply' || ev.event === 'check-error') { - if (ev.error) { - lifecycleError = ev.error - console.error('[ota] lifecycle', ev.event, 'error:', ev.error) - } - } - }) - } catch (e) { - console.warn('[ota] onLifecycle 监听失败:', e) - } - - try { - const newVersion = await mod.applyUpdate() - return newVersion - } catch (e) { - // 把 lifecycle 里的详细错误拼进抛出的异常, 方便前端展示真实原因 - const base = e?.message || '升级失败' - const detail = lifecycleError ? ` (${lifecycleError})` : '' - const err = new Error(base + detail) - err.cause = e - throw err - } finally { - if (unlisten) { - try { unlisten() } catch { /* ignore */ } - } - if (unlistenLifecycle) { - try { unlistenLifecycle() } catch { /* ignore */ } - } - } -} - -// 回滚到内置 dist (Tauri only, 一般用于 OTA 出错时手动恢复) -export async function rollbackOta() { - if (!inTauri) throw new Error('仅 Tauri 客户端可用') - const mod = await import('tauri-plugin-hotswap-api') - return mod.rollback() -} - -// 取当前 OTA 状态 (Tauri only) -export async function getOtaVersionInfo() { - if (!inTauri) return null - try { - const mod = await import('tauri-plugin-hotswap-api') - return await mod.getVersionInfo() - } catch (e) { - console.warn('[ota] getVersionInfo failed:', e) - return null - } -} - -// reload 当前页 (新资源生效) -export function reloadApp() { - window.location.reload() -} +// 前端 OTA 封装 +// - Tauri 客户端: 直接用 tauri-plugin-hotswap-api (checkUpdate / applyUpdate / ...) +// - Web 浏览器: 只走我们自己的 /api/ota/manifest, 提示用户手动刷新 (Vite 资源指纹会失效) +// +// 设计原则: +// - Web 端只能"查 + 提示", 不能真下载应用, 避免误导 +// - Tauri 端走 hotswap 插件的官方 API, 签名校验/回滚/资源替换全交给插件 +import { getApiBase } from './client.js' +import { APP_VERSION, BUILD_ID } from '../version.js' + +// 判断是否在 Tauri WebView 内 (有 __TAURI_INTERNALS__ 即 Tauri 环境) +const inTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window + +// 拼接绝对 URL: 在 Tauri (VITE_API_BASE 有值) 时直连, 否则同源 +function resolveUrl(p) { + const base = getApiBase() + return base ? base + p : p +} + +// 当前版本信息 (前端展示用) +export function getCurrentVersionInfo() { + return { version: APP_VERSION, build: BUILD_ID } +} + +// 构建标识统一化: 任意来源 (YYYYMMDDHHmmss / seq- / 毫秒数) → 'YYYYMMDDHHmmss' +// (北京时间, 与 vite BUILD_ID / OTA manifest.build_id 同格式; 无法解析返回 '') +export function fmtBuildId(v) { + if (v == null) return '' + if (typeof v === 'string' && v.startsWith('seq-')) return fmtBuildId(Number(v.slice(4))) + if (typeof v === 'string' && /^\d{14}$/.test(v)) return v + const ms = Number(v) + if (!Number.isFinite(ms) || ms <= 0) return '' + const d = new Date(ms + 8 * 3600 * 1000) + const p = (n, w) => String(n).padStart(w || 2, '0') + return `${d.getUTCFullYear()}${p(d.getUTCMonth() + 1)}${p(d.getUTCDate())}${p(d.getUTCHours())}${p(d.getUTCMinutes())}${p(d.getUTCSeconds())}` +} + +// 启动时: 确认当前资源可用 (Tauri only) +// 失败/异常都不抛, 仅 console.warn +export function initOta() { + if (!inTauri) return + // 动态 import: web 模式根本不打这个 chunk + import('tauri-plugin-hotswap-api') + .then((mod) => mod.notifyReady()) + .then(() => console.log('[ota] notifyReady ok')) + .catch((e) => console.warn('[ota] notifyReady failed:', e)) +} + +// --------------------------------------------------------------------------- +// 判新模式 (V0.2) +// 「开发版本」开关 (我的页 OTA 组内, 默认关闭), 值持久化在设备端 localStorage: +// 开关 关闭 (默认 / 生产口径): 用**版本号**判新 —— 取服务端 manifest 的 version, +// 与当前运行版本按数字字段逐段比较大小 (0.2.0 > 0.1.0)。 +// 这样即使某个旧版本分支被重新构建 (sequence 变大但版本号更小), 也不会把新设备拉回旧版。 +// 开关 打开 (开发口径): 用 **build sequence** 判新 —— 直接走插件 checkUpdate(), +// 只要服务端 sequence 比本地大就算新, 每次提交都能推到设备上, 便于联调。 +// --------------------------------------------------------------------------- +const OTA_DEV_MODE_KEY = 'iboard_ota_dev_mode' + +export function getOtaDevMode() { + try { + return localStorage.getItem(OTA_DEV_MODE_KEY) === '1' + } catch { /* ignore */ } + return false +} + +export function setOtaDevMode(v) { + try { localStorage.setItem(OTA_DEV_MODE_KEY, v ? '1' : '0') } catch { /* ignore */ } +} + +// 版本号 → 数字字段数组 / 比较函数 (纯函数放在 utils, 便于单测) +import { compareVersion, parseVersionFields } from '../utils/version.js' + +export { compareVersion, parseVersionFields } + +// 拉服务端最新 manifest (不分平台) +// /api/ota/check/0: current_sequence 传 0, 只要有 OTA 包就一定返回 200 + manifest +export async function fetchOtaManifest() { + try { + const res = await fetch(resolveUrl('/api/ota/check/0'), { method: 'GET' }) + if (res.status === 204) return null + if (!res.ok) return null + return await res.json() + } catch (e) { + console.warn('[ota] manifest 拉取失败:', e) + return null + } +} + +// 检查更新 +// 返回统一结构, 调用方不关心是 Tauri 还是 web: +// { +// available: boolean, +// reason: 'no_package' | 'same_version' | 'ota_disabled' | 'unsupported' | 'ok', +// info: { version, sequence, notes, mandatory, bundle_size } +// } +// opts.devMode: true=按 build sequence 判新, false=按版本号判新 (默认) +export async function checkOtaUpdate(opts) { + const devMode = !!(opts && opts.devMode) + if (!inTauri) { + return checkOtaUpdateWeb(devMode) + } + return checkOtaUpdateTauri(devMode) +} + +// 取"当前运行版本": 已应用 OTA 覆盖包则用覆盖包版本, 否则用内置包版本 +async function currentRunningVersion() { + try { + const v = await getOtaVersionInfo() + if (v?.active && v.version) return v.version + } catch { /* ignore */ } + return APP_VERSION +} + +// 版本号口径判新 (Tauri + Web 共用): 服务端 version 比当前运行版本大才算新 +async function checkByVersion() { + const [manifest, current] = await Promise.all([fetchOtaManifest(), currentRunningVersion()]) + if (!manifest || !manifest.version) { + return { available: false, reason: 'no_package', info: null } + } + if (compareVersion(manifest.version, current) <= 0) { + return { available: false, reason: 'same_version', info: manifest } + } + return { + available: true, + reason: 'ok', + info: { + version: manifest.version, + sequence: manifest.sequence, + build: manifest.build_id || fmtBuildId(manifest.sequence), + notes: manifest.notes || '', + commit: manifest.commit || '', + mandatory: !!manifest.mandatory, + bundle_size: manifest.bundle_size || 0, + }, + } +} + +async function checkOtaUpdateTauri(devMode) { + // 开发口径: sequence 判新, 直接走插件 (插件内部用当前 sequence 请求 /api/ota/check/:seq) + if (devMode) { + const { checkUpdate } = await import('tauri-plugin-hotswap-api') + const r = await checkUpdate() + if (!r.available) { + return { available: false, reason: 'no_package', info: r } + } + return { + available: true, + reason: 'ok', + info: { + version: r.version, + sequence: r.sequence, + build: fmtBuildId(r.sequence), + notes: r.notes, + mandatory: r.mandatory, + bundle_size: r.bundle_size, + }, + } + } + // 生产口径: 版本号判新 + return checkByVersion() +} + +async function checkOtaUpdateWeb(devMode) { + // Web 端不能下载, 只能拿到版本号/构建号做比对, 提示用户刷新 + try { + const res = await fetch(resolveUrl('/api/ota/manifest-web'), { method: 'GET' }) + if (!res.ok) { + return { available: false, reason: 'unsupported', info: null } + } + const data = await res.json() + if (!data.available) return { available: false, reason: 'no_package', info: data } + // Web 端拿不到本地 sequence: + // 开发口径 (devMode) → 只要服务端版本与本地不同就算新 (近似 sequence 语义, 含回退) + // 生产口径 → 严格比较版本号大小, 只有更高版本才算新 + const cmp = compareVersion(data.version, APP_VERSION) + if (devMode ? cmp === 0 : cmp <= 0) { + return { available: false, reason: 'same_version', info: data } + } + return { + available: true, + reason: 'ok', + info: { + version: data.version, + sequence: data.sequence, + build: fmtBuildId(data.build_id || data.sequence), + notes: data.note || '', + commit: data.commit || '', + // web 端没有强制/mandatory 等字段, 仅展示 + mandatory: false, + bundle_size: data.size || 0, + }, + } + } catch (e) { + console.warn('[ota] web check failed:', e) + return { available: false, reason: 'unsupported', info: null } + } +} + +// 下载并应用更新 (Tauri only). 调用方负责 UI 提示和错误处理 +// onProgress(received, total) 进度回调 +// 成功后必须 reload, 新资源才会生效 +export async function applyOtaUpdate(onProgress) { + if (!inTauri) { + throw new Error('OTA 升级仅在 Tauri 客户端可用, Web 端请直接刷新页面') + } + const mod = await import('tauri-plugin-hotswap-api') + + // 注册一次性进度监听 (applyUpdate 期间触发) + let unlisten = null + let unlistenLifecycle = null + if (onProgress) { + try { + unlisten = await mod.onDownloadProgress((p) => onProgress(p.downloaded, p.total)) + } catch (e) { + console.warn('[ota] onDownloadProgress 监听失败:', e) + } + } + + // 监听 lifecycle 事件, 捕获失败详情 (download-error / apply 失败时 error 字段带具体原因) + // applyUpdate() 抛出的错误往往只有泛化信息, 细节藏在 lifecycle 事件里 + let lifecycleError = '' + try { + unlistenLifecycle = await mod.onLifecycle((ev) => { + if (ev.event === 'download-error' || ev.event === 'apply' || ev.event === 'check-error') { + if (ev.error) { + lifecycleError = ev.error + console.error('[ota] lifecycle', ev.event, 'error:', ev.error) + } + } + }) + } catch (e) { + console.warn('[ota] onLifecycle 监听失败:', e) + } + + try { + const newVersion = await mod.applyUpdate() + return newVersion + } catch (e) { + // 把 lifecycle 里的详细错误拼进抛出的异常, 方便前端展示真实原因 + const base = e?.message || '升级失败' + const detail = lifecycleError ? ` (${lifecycleError})` : '' + const err = new Error(base + detail) + err.cause = e + throw err + } finally { + if (unlisten) { + try { unlisten() } catch { /* ignore */ } + } + if (unlistenLifecycle) { + try { unlistenLifecycle() } catch { /* ignore */ } + } + } +} + +// 回滚到内置 dist (Tauri only, 一般用于 OTA 出错时手动恢复) +export async function rollbackOta() { + if (!inTauri) throw new Error('仅 Tauri 客户端可用') + const mod = await import('tauri-plugin-hotswap-api') + return mod.rollback() +} + +// 取当前 OTA 状态 (Tauri only) +export async function getOtaVersionInfo() { + if (!inTauri) return null + try { + const mod = await import('tauri-plugin-hotswap-api') + return await mod.getVersionInfo() + } catch (e) { + console.warn('[ota] getVersionInfo failed:', e) + return null + } +} + +// reload 当前页 (新资源生效) +export function reloadApp() { + window.location.reload() +} diff --git a/src/api/task.js b/src/api/task.js new file mode 100644 index 0000000..b8e44ba --- /dev/null +++ b/src/api/task.js @@ -0,0 +1,775 @@ +// 任务模块数据层 (0.2.1): 已切换为 PowerSync 本地 SQLite +// - 读: 全部来自本地库 (离线可用); 写: 本地写入 -> upload queue -> /api/powersync/upload (按 uuid 幂等) +// - 对页面保持与原 REST 版一致的调用签名与响应形状 (projectApi / columnApi / taskApi) +// - 编号: Project.code / Task.number 由服务端统一分配, 本地新建行同步前分别为空 -> 展示占位 +// 见 docs/V0.2/离线编辑模块/0.2.1实施方案.md +import { getPowersyncDb } from '../lib/powersync/db.js' +import { api, getCurrentUserId } from './client.js' +import { filterVisible } from '../pages/task/date.js' + +const db = () => getPowersyncDb() + +// crypto.randomUUID 仅在安全上下文 (https / localhost) 可用; 部署站为 http 明文, 用 getRandomValues 兜底生成 v4 +const uuid = () => { + if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') return crypto.randomUUID() + const b = crypto.getRandomValues(new Uint8Array(16)) + b[6] = (b[6] & 0x0f) | 0x40 + b[8] = (b[8] & 0x3f) | 0x80 + const h = [...b].map((x) => x.toString(16).padStart(2, '0')).join('') + return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}` +} +const now = () => new Date().toISOString() +const parseJson = (v, fallback) => { + if (v === null || v === undefined || v === '') return fallback + if (typeof v !== 'string') return v + try { return JSON.parse(v) } catch { return fallback } +} + +// 新建项目时的默认 4 列 (与服务端 DEFAULT_COLUMNS 一致) +const DEFAULT_COLUMNS = [ + { name: 'Plan', sortOrder: 0, boundStatus: 'todo' }, + { name: 'Doing', sortOrder: 1, boundStatus: 'doing' }, + { name: 'Action', sortOrder: 2, boundStatus: null }, + { name: 'Done', sortOrder: 3, boundStatus: 'done' }, +] +export const DEFAULT_TASK_KINDS = ['需求', 'Bug', 'FEATURE', '优化'] +const MAX_NAME = 20 + +function normTaskKinds(v) { + if (!Array.isArray(v)) return null + const out = [] + for (const k of v) { + const s = String(k || '').trim().slice(0, 20) + if (s && !out.includes(s)) out.push(s) + if (out.length >= 30) break + } + return out +} + +// ---------- 查询助手 ---------- + +async function getColumns(projectId) { + return db().getAll( + 'SELECT * FROM BoardColumn WHERE projectId = ? ORDER BY sortOrder, id', + [String(projectId)], + ) +} + +async function getProjectMembers(projectId) { + const rows = await db().getAll( + `SELECT pm.userId, pm.permission, u.name, u.email + FROM ProjectMember pm LEFT JOIN User u ON u.id = pm.userId + WHERE pm.projectId = ? ORDER BY pm.createdAt, pm.id`, + [String(projectId)], + ) + return rows.map((r) => ({ userId: String(r.userId), name: r.name || String(r.userId), email: r.email || '', permission: r.permission || 'member' })) +} + +function memberMapOf(members) { + return new Map(members.map((m) => [m.userId, { id: m.userId, name: m.name }])) +} + +async function shapeTasks(rows, columns) { + const columnById = new Map(columns.map((c) => [String(c.id), c])) + const projectIds = [...new Set(rows.map((r) => String(r.projectId)))] + let membersByProject = new Map() + if (projectIds.length) { + const ph = projectIds.map(() => '?').join(',') + const pmRows = await db().getAll( + `SELECT pm.projectId, pm.userId, u.name FROM ProjectMember pm LEFT JOIN User u ON u.id = pm.userId WHERE pm.projectId IN (${ph})`, + projectIds, + ) + membersByProject = new Map(projectIds.map((pid) => [pid, new Map()])) + for (const r of pmRows) { + membersByProject.get(String(r.projectId)).set(String(r.userId), { id: String(r.userId), name: r.name || String(r.userId) }) + } + } + const taskIds = rows.map((r) => String(r.id)) + const memberRows = taskIds.length + ? await db().getAll( + `SELECT tm.taskId, tm.userId, u.name FROM TaskMember tm LEFT JOIN User u ON u.id = tm.userId WHERE tm.taskId IN (${taskIds.map(() => '?').join(',')})`, + taskIds, + ) + : [] + const tagRows = taskIds.length + ? await db().getAll(`SELECT taskId, name FROM TaskTag WHERE taskId IN (${taskIds.map(() => '?').join(',')})`, taskIds) + : [] + const membersByTask = new Map() + for (const r of memberRows) { + const k = String(r.taskId) + if (!membersByTask.has(k)) membersByTask.set(k, []) + membersByTask.get(k).push({ userId: String(r.userId), name: r.name || String(r.userId) }) + } + const tagsByTask = new Map() + for (const r of tagRows) { + const k = String(r.taskId) + if (!tagsByTask.has(k)) tagsByTask.set(k, []) + tagsByTask.get(k).push(r.name) + } + + const shapeOne = (t) => { + const members = membersByTask.get(String(t.id)) || [] + const usersByProject = membersByProject.get(String(t.projectId)) || new Map() + return { + ...t, + extras: parseJson(t.extras, []), + column: columnById.get(String(t.columnId)) || null, + assignee: t.assigneeId ? (usersByProject.get(String(t.assigneeId)) || { id: String(t.assigneeId), name: String(t.assigneeId) }) : null, + members, + tags: tagsByTask.get(String(t.id)) || [], + } + } + return shapeOne +} + +function projectSummary(p, memberCount, taskCount) { + return { + id: String(p.id), + uuid: p.uuid, + code: p.code || null, + name: p.name, + ownerId: String(p.ownerId), + isPersonal: !!p.isPersonal, + archived: !!p.archived, + createdAt: p.createdAt, + updatedAt: p.updatedAt, + taskKinds: normTaskKinds(parseJson(p.taskKinds, null)) || DEFAULT_TASK_KINDS, + icon: p.icon || null, + memberCount, + taskCount, + } +} + +function assertUser() { + const uid = getCurrentUserId() + if (!uid) throw new Error('未登录') + return String(uid) +} + +// ---------- 项目 ---------- + +export const projectApi = { + // 我参与的项目 (本地库; 个人默认项目缺失且在线时回源一次让服务端补建) + list: async () => { + const uid = assertUser() + let rows = await db().getAll( + `SELECT p.* FROM Project p WHERE p.id IN (SELECT pm.projectId FROM ProjectMember pm WHERE pm.userId = ?) + ORDER BY p.archived, p.updatedAt DESC`, + [uid], + ) + if (!rows.some((r) => !!r.isPersonal && String(r.ownerId) === uid)) { + try { + await api('/projects') + await new Promise((r) => setTimeout(r, 1200)) + rows = await db().getAll( + `SELECT p.* FROM Project p WHERE p.id IN (SELECT pm.projectId FROM ProjectMember pm WHERE pm.userId = ?) + ORDER BY p.archived, p.updatedAt DESC`, + [uid], + ) + } catch { /* 离线: 无个人项目则跳过 */ } + } + const items = [] + for (const p of rows) { + const memberCount = await db().getOptional('SELECT COUNT(*) AS n FROM ProjectMember WHERE projectId = ?', [String(p.id)]) + const taskCount = await db().getOptional('SELECT COUNT(*) AS n FROM Task WHERE projectId = ?', [String(p.id)]) + items.push(projectSummary(p, memberCount?.n || 0, taskCount?.n || 0)) + } + return { items } + }, + + detail: async (id) => { + const p = await db().getOptional('SELECT * FROM Project WHERE id = ?', [String(id)]) + if (!p) throw new Error('项目不存在或无权限') + const members = await getProjectMembers(p.id) + const columns = await getColumns(p.id) + const taskCount = await db().getOptional('SELECT COUNT(*) AS n FROM Task WHERE projectId = ?', [String(p.id)]) + return { + item: { + ...projectSummary(p, members.length, taskCount?.n || 0), + members, + columns, + notes: parseJson(p.notes, []), + }, + } + }, + + create: async (body) => { + const uid = assertUser() + const name = String(body?.name || '').trim() + if (!name) throw new Error('项目名称不能为空') + if (name.length > MAX_NAME) throw new Error(`项目名称不能超过 ${MAX_NAME} 字`) + let code = null + const rawCode = body?.code + if (rawCode !== undefined && rawCode !== null && String(rawCode).trim() !== '') { + code = String(rawCode).trim().toUpperCase() + if (!/^[A-Z0-9]{2,8}$/.test(code)) throw new Error('项目编号须为 2~8 位字母数字') + const dup = await db().getOptional('SELECT id FROM Project WHERE code = ?', [code]) + if (dup) throw new Error(`项目编号 ${code} 已被占用`) + } + const id = uuid() + const ts = now() + await db().writeTransaction(async (tx) => { + await tx.execute( + 'INSERT INTO Project (id, uuid, code, name, ownerId, isPersonal, archived, notes, taskKinds, icon, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + [id, id, code, name, uid, 0, 0, '[]', null, null, ts, ts], + ) + await tx.execute( + 'INSERT INTO ProjectMember (id, uuid, projectId, userId, permission, createdAt) VALUES (?, ?, ?, ?, ?, ?)', + [uuid(), uuid(), id, uid, 'member', ts], + ) + for (const c of DEFAULT_COLUMNS) { + await tx.execute( + 'INSERT INTO BoardColumn (id, uuid, projectId, name, sortOrder, boundStatus, retention, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', + [uuid(), uuid(), id, c.name, c.sortOrder, c.boundStatus, 'forever', ts, ts], + ) + } + }) + const p = await db().get('SELECT * FROM Project WHERE id = ?', [id]) + return { item: projectSummary(p, 1, 0) } + }, + + update: async (id, body) => { + await db().getOptional('SELECT id FROM Project WHERE id = ?', [String(id)]).then((p) => { + if (!p) throw new Error('项目不存在或无权限') + }) + const data = {} + if (body?.name !== undefined) { + const t = String(body.name).trim() + if (!t) throw new Error('项目名称不能为空') + if (t.length > MAX_NAME) throw new Error(`项目名称不能超过 ${MAX_NAME} 字`) + data.name = t + } + if (body?.code !== undefined) { + const c = String(body.code || '').trim().toUpperCase() + if (!/^[A-Z0-9]{2,8}$/.test(c)) throw new Error('项目编号须为 2~8 位字母数字') + const dup = await db().getOptional('SELECT id FROM Project WHERE code = ? AND id != ?', [c, String(id)]) + if (dup) throw new Error(`项目编号 ${c} 已被占用`) + data.code = c + } + if (body?.taskKinds !== undefined) { + const kinds = normTaskKinds(body.taskKinds) + if (!kinds?.length) throw new Error('至少保留一个任务类型') + data.taskKinds = JSON.stringify(kinds) + } + if (body?.icon !== undefined) { + if (body.icon === null) data.icon = null + else { + const s = String(body.icon) + if (!s.startsWith('data:image/')) throw new Error('图标格式非法') + if (s.length > 300_000) throw new Error('图标过大') + data.icon = s + } + } + if (body?.archived !== undefined) data.archived = body.archived ? 1 : 0 + if (body?.notes !== undefined) { + if (!Array.isArray(body.notes)) throw new Error('信息块格式非法') + data.notes = JSON.stringify(body.notes.slice(0, 50).map((n) => ({ + title: String(n?.title ?? '').slice(0, 50), + content: String(n?.content ?? '').slice(0, 20000), + }))) + } + const sets = Object.keys(data).map((k) => `"${k}" = ?`) + if (sets.length) { + await db().execute( + `UPDATE Project SET ${sets.join(', ')}, updatedAt = ? WHERE id = ?`, + [...Object.values(data), now(), String(id)], + ) + } + const p = await db().get('SELECT * FROM Project WHERE id = ?', [String(id)]) + const memberCount = await db().getOptional('SELECT COUNT(*) AS n FROM ProjectMember WHERE projectId = ?', [String(p.id)]) + const taskCount = await db().getOptional('SELECT COUNT(*) AS n FROM Task WHERE projectId = ?', [String(p.id)]) + return { item: projectSummary(p, memberCount?.n || 0, taskCount?.n || 0) } + }, + + remove: async (id) => { + const p = await db().getOptional('SELECT * FROM Project WHERE id = ?', [String(id)]) + if (!p) throw new Error('项目不存在或无权限') + if (!!p.isPersonal) throw new Error('个人默认项目不可删除') + // 手动级联 (本地 SQLite 无外键级联) + await db().writeTransaction(async (tx) => { + const taskIds = (await tx.getAll('SELECT id FROM Task WHERE projectId = ?', [String(p.id)])).map((r) => String(r.id)) + if (taskIds.length) { + const ph = taskIds.map(() => '?').join(',') + await tx.execute(`DELETE FROM TaskMember WHERE taskId IN (${ph})`, taskIds) + await tx.execute(`DELETE FROM TaskTag WHERE taskId IN (${ph})`, taskIds) + } + await tx.execute('DELETE FROM Task WHERE projectId = ?', [String(p.id)]) + await tx.execute('DELETE FROM BoardColumn WHERE projectId = ?', [String(p.id)]) + await tx.execute('DELETE FROM ProjectMember WHERE projectId = ?', [String(p.id)]) + await tx.execute('DELETE FROM Project WHERE id = ?', [String(p.id)]) + }) + return { ok: true } + }, + + addMember: async (id, body) => { + const p = await db().getOptional('SELECT * FROM Project WHERE id = ?', [String(id)]) + if (!p) throw new Error('项目不存在或无权限') + if (!!p.isPersonal) throw new Error('个人默认项目不可添加成员') + let target = null + if (body?.userId) target = await db().getOptional('SELECT id, name FROM User WHERE id = ?', [String(body.userId)]) + else if (body?.email) target = await db().getOptional('SELECT id, name FROM User WHERE email = ?', [String(body.email).trim()]) + if (!target) throw new Error('用户不存在') + const dup = await db().getOptional('SELECT id FROM ProjectMember WHERE projectId = ? AND userId = ?', [String(p.id), String(target.id)]) + if (dup) throw new Error('该用户已是项目成员') + await db().execute( + 'INSERT INTO ProjectMember (id, uuid, projectId, userId, permission, createdAt) VALUES (?, ?, ?, ?, ?, ?)', + [uuid(), uuid(), String(p.id), String(target.id), 'member', now()], + ) + return { ok: true, member: { userId: String(target.id), name: target.name } } + }, + + removeMember: async (id, uid) => { + const p = await db().getOptional('SELECT * FROM Project WHERE id = ?', [String(id)]) + if (!p) throw new Error('项目不存在或无权限') + if (!!p.isPersonal) throw new Error('个人默认项目不可移除成员') + const me = getCurrentUserId() + if (String(uid) !== me && String(p.ownerId) !== me) throw new Error('只能移除自己,或由创建人移除他人') + await db().execute('DELETE FROM ProjectMember WHERE projectId = ? AND userId = ?', [String(p.id), String(uid)]) + return { ok: true } + }, +} + +// ---------- 看板列 ---------- + +export const columnApi = { + list: async (projectId) => ({ items: await getColumns(projectId) }), + + add: async (projectId, body) => { + const name = String(body?.name || '').trim() + if (!name) throw new Error('列名不能为空') + if (name.length > MAX_NAME) throw new Error(`列名不能超过 ${MAX_NAME} 字`) + const cols = await getColumns(projectId) + if (cols.length >= 10) throw new Error('最多 10 列') + if (cols.some((c) => c.name === name)) throw new Error('列名已存在') + const id = uuid() + const ts = now() + await db().execute( + 'INSERT INTO BoardColumn (id, uuid, projectId, name, sortOrder, boundStatus, retention, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', + [id, id, String(projectId), name, cols.length ? Math.max(...cols.map((c) => Number(c.sortOrder))) + 1 : 0, null, 'forever', ts, ts], + ) + return { item: await db().get('SELECT * FROM BoardColumn WHERE id = ?', [id]) } + }, + + update: async (id, body) => { + const col = await db().getOptional('SELECT * FROM BoardColumn WHERE id = ?', [String(id)]) + if (!col) throw new Error('列不存在') + const data = {} + if (body?.name !== undefined) { + const t = String(body.name).trim() + if (!t) throw new Error('列名不能为空') + if (t.length > MAX_NAME) throw new Error(`列名不能超过 ${MAX_NAME} 字`) + const dup = await db().getOptional('SELECT id FROM BoardColumn WHERE projectId = ? AND name = ? AND id != ?', [String(col.projectId), t, String(id)]) + if (dup) throw new Error('列名已存在') + data.name = t + } + if (body?.boundStatus !== undefined) { + if (body.boundStatus !== null && !['todo', 'doing', 'done'].includes(body.boundStatus)) throw new Error('绑定状态取值非法') + data.boundStatus = body.boundStatus + } + if (body?.retention !== undefined) { + if (!['never', 'today', '3d', 'week', 'month', 'forever'].includes(body.retention)) throw new Error('保留选项取值非法') + data.retention = body.retention + } + const sets = Object.keys(data).map((k) => `"${k}" = ?`) + if (sets.length) { + await db().execute(`UPDATE BoardColumn SET ${sets.join(', ')}, updatedAt = ? WHERE id = ?`, [...Object.values(data), now(), String(id)]) + } + return { item: await db().get('SELECT * FROM BoardColumn WHERE id = ?', [String(id)]) } + }, + + remove: async (id, moveToColumnId) => { + const col = await db().getOptional('SELECT * FROM BoardColumn WHERE id = ?', [String(id)]) + if (!col) throw new Error('列不存在') + const cols = await getColumns(col.projectId) + if (cols.length <= 1) throw new Error('至少保留一列') + let targetId = String(moveToColumnId || '') + if (!targetId) { + const idx = cols.findIndex((c) => String(c.id) === String(id)) + targetId = String((cols[idx + 1] || cols[idx - 1]).id) + } + const target = cols.find((c) => String(c.id) === targetId) + if (!target || String(target.id) === String(id)) throw new Error('迁移目标列非法') + await db().writeTransaction(async (tx) => { + // 迁移任务: 状态按目标列绑定重新裁决 (与服务端同口径) + if (target.boundStatus) { + await tx.execute('UPDATE Task SET columnId = ?, status = ?, updatedAt = ? WHERE columnId = ?', [targetId, target.boundStatus, now(), String(id)]) + } else { + await tx.execute('UPDATE Task SET columnId = ?, updatedAt = ? WHERE columnId = ?', [targetId, now(), String(id)]) + } + await tx.execute('DELETE FROM BoardColumn WHERE id = ?', [String(id)]) + }) + return { ok: true, toColumnId: targetId } + }, + + order: async (projectId, ids) => { + const list = Array.isArray(ids) ? ids.map(String) : [] + if (!list.length) throw new Error('ids 不能为空') + await db().writeTransaction(async (tx) => { + for (let i = 0; i < list.length; i++) { + await tx.execute('UPDATE BoardColumn SET sortOrder = ?, updatedAt = ? WHERE id = ?', [i, now(), list[i]]) + } + }) + return { items: await getColumns(projectId) } + }, +} + +// ---------- 任务 ---------- + +function assertTitle(t) { + const s = String(t || '').trim() + if (!s) throw new Error('任务标题不能为空') + if (s.length > 30) throw new Error('任务标题不能超过 30 字') + return s +} + +function parseMarkDate(v) { + if (v === null || v === undefined || v === '') return null + const d = new Date(v) + if (Number.isNaN(d.getTime())) throw new Error('时间标记格式非法') + return d.toISOString() +} + +export const taskApi = { + list: async (projectId) => { + const columns = await getColumns(projectId) + const rows = await db().getAll( + 'SELECT * FROM Task WHERE projectId = ? AND parentId IS NULL AND archived = 0 ORDER BY sortOrder, createdAt, id', + [String(projectId)], + ) + const shape = await shapeTasks(rows, columns) + const shaped = rows.map(shape) + // 子任务挂载 (与服务端 TASK_INCLUDE.children 同构) + for (const t of shaped) { + const childRows = await db().getAll( + 'SELECT * FROM Task WHERE parentId = ? ORDER BY sortOrder, id', + [String(t.id)], + ) + const childShape = await shapeTasks(childRows, columns) + t.children = childRows.map(childShape) + } + const visible = filterVisible(shaped, columns) + return { items: visible, columns, total: shaped.length } + }, + + create: async (projectId, body) => { + const uid = assertUser() + const title = assertTitle(body?.title) + const columns = await getColumns(projectId) + if (!columns.length) throw new Error('项目没有可用的看板列') + let parentId = null + if (body?.parentId) { + const parent = await db().getOptional('SELECT * FROM Task WHERE id = ?', [String(body.parentId)]) + if (!parent) throw new Error('父任务不存在') + if (String(parent.projectId) !== String(projectId)) throw new Error('子任务必须与父任务同项目') + if (parent.parentId) throw new Error('仅支持一级子任务') + parentId = String(parent.id) + } + const memberIds = await db().getAll('SELECT userId FROM ProjectMember WHERE projectId = ?', [String(projectId)]) + const memberSet = new Set(memberIds.map((r) => String(r.userId))) + let finalAssignee = null + if (body?.assigneeId === undefined) finalAssignee = memberSet.has(uid) ? uid : null + else if (body?.assigneeId !== null) { + const aid = String(body.assigneeId) + if (!memberSet.has(aid)) throw new Error('负责人必须是项目成员') + finalAssignee = aid + } + const firstCol = columns[0] + const id = uuid() + const ts = now() + await db().writeTransaction(async (tx) => { + await tx.execute( + `INSERT INTO Task (id, uuid, projectId, columnId, parentId, title, description, kind, markDate, status, doneAt, assigneeId, creatorId, extras, sortOrder, number, archived, archivedAt, archiveSource, createdAt, updatedAt) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, NULL, NULL, ?, ?)`, + [id, id, String(projectId), String(firstCol.id), parentId, title, String(body?.description || ''), + body?.kind ? String(body.kind).trim().slice(0, 20) : null, parseMarkDate(body?.markDate), + firstCol.boundStatus || 'todo', null, finalAssignee, uid, '[]', 0, null, ts, ts], + ) + for (const mid of (Array.isArray(body?.memberIds) ? body.memberIds : []).map(String).filter((x) => memberSet.has(x))) { + await tx.execute('INSERT INTO TaskMember (id, uuid, taskId, userId) VALUES (?, ?, ?, ?)', [uuid(), uuid(), id, mid]) + } + for (const name of (Array.isArray(body?.tags) ? body.tags : []).filter(Boolean).map((n) => String(n).slice(0, 20))) { + await tx.execute('INSERT INTO TaskTag (id, uuid, taskId, name) VALUES (?, ?, ?, ?)', [uuid(), uuid(), id, name]) + } + }) + const row = await db().get('SELECT * FROM Task WHERE id = ?', [id]) + const shape = await shapeTasks([row], columns) + return { item: shape(row) } + }, + + detail: async (id) => { + const row = await db().getOptional('SELECT * FROM Task WHERE id = ?', [String(id)]) + if (!row) throw new Error('任务不存在或无权限') + const columns = await getColumns(row.projectId) + const shape = await shapeTasks([row], columns) + return { item: shape(row) } + }, + + update: async (id, body) => { + const task = await db().getOptional('SELECT * FROM Task WHERE id = ?', [String(id)]) + if (!task) throw new Error('任务不存在或无权限') + const columns = await getColumns(task.projectId) + const data = {} + + if (body?.title !== undefined) data.title = assertTitle(body.title) + if (body?.description !== undefined) data.description = String(body.description) + if (body?.kind !== undefined) data.kind = body.kind ? String(body.kind).trim().slice(0, 20) : null + if (body?.markDate !== undefined) data.markDate = parseMarkDate(body.markDate) + if (body?.extras !== undefined) { + if (!Array.isArray(body.extras)) throw new Error('扩展信息格式非法') + data.extras = JSON.stringify(body.extras.map((e) => ({ + label: String(e?.label ?? '').slice(0, 40), + value: String(e?.value ?? ''), + url: e?.url ? String(e.url) : '', + }))) + } + if (body?.sortOrder !== undefined) data.sortOrder = Number(body.sortOrder) || 0 + + const memberRows = await db().getAll('SELECT userId FROM ProjectMember WHERE projectId = ?', [String(task.projectId)]) + const memberSet = new Set(memberRows.map((r) => String(r.userId))) + if (body?.assigneeId !== undefined) { + if (body.assigneeId === null || body.assigneeId === '') data.assigneeId = null + else { + const aid = String(body.assigneeId) + if (!memberSet.has(aid)) throw new Error('负责人必须是项目成员') + data.assigneeId = aid + } + } + + // 列 / 状态联动裁决 (与 server/task-domain/resolveStatus.js 同口径) + let status = task.status + let columnId = String(task.columnId) + const patch = {} + if (body?.columnId !== undefined) patch.columnId = String(body.columnId) + if (body?.status !== undefined) { + if (!['todo', 'doing', 'done'].includes(body.status)) throw new Error('任务状态取值非法') + patch.status = body.status + } + if (patch.columnId !== undefined && !columns.some((c) => String(c.id) === patch.columnId)) { + throw new Error('看板列不存在') + } + if (Object.keys(patch).length) { + status = patch.status !== undefined ? patch.status : task.status + columnId = patch.columnId !== undefined ? patch.columnId : String(task.columnId) + if (!task.parentId) { + const ordered = [...columns].sort((a, b) => (Number(a.sortOrder) - Number(b.sortOrder)) || String(a.id).localeCompare(String(b.id))) + const targetColumn = ordered.find((c) => String(c.id) === columnId) + if (patch.columnId !== undefined && targetColumn) { + if (targetColumn.boundStatus) status = targetColumn.boundStatus + } + if (patch.status !== undefined) { + if (!(targetColumn && targetColumn.boundStatus === status)) { + const hit = ordered.find((c) => c.boundStatus === status) + if (hit) columnId = String(hit.id) + } + } + } + data.status = status + data.columnId = columnId + } + if (data.status === 'done' && task.status !== 'done') data.doneAt = now() + else if (data.status !== undefined && data.status !== 'done') data.doneAt = null + + // 拖拽降为子任务 + if (body?.parentId !== undefined) { + if (body.parentId === null) { + data.parentId = null + } else { + const pid = String(body.parentId) + if (pid === String(task.id)) throw new Error('不能成为自己的子任务') + const parent = await db().getOptional('SELECT * FROM Task WHERE id = ?', [pid]) + if (!parent) throw new Error('目标任务不存在') + if (String(parent.projectId) !== String(task.projectId)) throw new Error('子任务必须与父任务同项目') + if (parent.parentId) throw new Error('仅支持一级子任务') + const childCount = await db().getOptional('SELECT COUNT(*) AS n FROM Task WHERE parentId = ?', [String(task.id)]) + if ((childCount?.n || 0) > 0) throw new Error('该任务已有子任务,不能成为子任务') + data.parentId = pid + } + } + + await db().writeTransaction(async (tx) => { + const sets = Object.keys(data).map((k) => `"${k}" = ?`) + if (sets.length) { + await tx.execute(`UPDATE Task SET ${sets.join(', ')}, updatedAt = ? WHERE id = ?`, [...Object.values(data), now(), String(task.id)]) + } + if (Array.isArray(body?.memberIds)) { + const ids = body.memberIds.map(String).filter((x) => memberSet.has(x)) + await tx.execute('DELETE FROM TaskMember WHERE taskId = ?', [String(task.id)]) + for (const mid of ids) { + await tx.execute('INSERT INTO TaskMember (id, uuid, taskId, userId) VALUES (?, ?, ?, ?)', [uuid(), uuid(), String(task.id), mid]) + } + } + if (Array.isArray(body?.tags)) { + await tx.execute('DELETE FROM TaskTag WHERE taskId = ?', [String(task.id)]) + const names = body.tags.map((n) => String(n).trim()).filter(Boolean).slice(0, 10) + for (const name of names) { + await tx.execute('INSERT INTO TaskTag (id, uuid, taskId, name) VALUES (?, ?, ?, ?)', [uuid(), uuid(), String(task.id), name.slice(0, 20)]) + } + } + }) + const row = await db().get('SELECT * FROM Task WHERE id = ?', [String(task.id)]) + const shape = await shapeTasks([row], columns) + return { item: shape(row) } + }, + + reorder: async (projectId, ids) => { + const list = Array.isArray(ids) ? ids.map(String).filter(Boolean) : [] + if (!list.length) throw new Error('ids 不能为空') + await db().writeTransaction(async (tx) => { + for (let i = 0; i < list.length; i++) { + await tx.execute('UPDATE Task SET sortOrder = ?, updatedAt = ? WHERE id = ?', [i, now(), list[i]]) + } + }) + return { ok: true } + }, + + remove: async (id) => { + const task = await db().getOptional('SELECT * FROM Task WHERE id = ?', [String(id)]) + if (!task) throw new Error('任务不存在或无权限') + const childCount = await db().getOptional('SELECT COUNT(*) AS n FROM Task WHERE parentId = ?', [String(task.id)]) + await db().writeTransaction(async (tx) => { + const ids = [String(task.id), ...((await tx.getAll('SELECT id FROM Task WHERE parentId = ?', [String(task.id)])).map((r) => String(r.id)))] + const ph = ids.map(() => '?').join(',') + await tx.execute(`DELETE FROM TaskMember WHERE taskId IN (${ph})`, ids) + await tx.execute(`DELETE FROM TaskTag WHERE taskId IN (${ph})`, ids) + await tx.execute(`DELETE FROM Task WHERE id IN (${ph})`, ids) + }) + return { ok: true, deletedSubtasks: childCount?.n || 0 } + }, + + copy: async (id, projectId) => { + const uid = assertUser() + const source = await db().getOptional('SELECT * FROM Task WHERE id = ?', [String(id)]) + if (!source) throw new Error('任务不存在或无权限') + const targetProject = await db().getOptional('SELECT * FROM Project WHERE id = ?', [String(projectId)]) + if (!targetProject) throw new Error('目标项目不存在或你不是其成员') + const targetColumns = await getColumns(targetProject.id) + if (!targetColumns.length) throw new Error('目标项目没有可用的看板列') + const sourceColumns = await getColumns(source.projectId) + const targetMemberIds = new Set((await db().getAll('SELECT userId FROM ProjectMember WHERE projectId = ?', [String(targetProject.id)])).map((r) => String(r.userId))) + + const pickColumn = (sourceColumnId) => { + const src = sourceColumns.find((c) => String(c.id) === String(sourceColumnId)) + if (src) { + const same = targetColumns.find((c) => c.name === src.name) + if (same) return String(same.id) + } + return String(targetColumns[0]?.id) + } + + const copyOne = async (tx, s, parentId) => { + const newId = uuid() + const ts = now() + await tx.execute( + `INSERT INTO Task (id, uuid, projectId, columnId, parentId, title, description, kind, markDate, status, doneAt, assigneeId, creatorId, extras, sortOrder, number, createdAt, updatedAt) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [newId, newId, String(targetProject.id), pickColumn(s.columnId), parentId, s.title, s.description || '', s.kind || null, + s.markDate || null, s.status, s.status === 'done' ? (s.doneAt || ts) : null, + s.assigneeId && targetMemberIds.has(String(s.assigneeId)) ? String(s.assigneeId) : null, + uid, s.extras || '[]', Number(s.sortOrder) || 0, null, ts, ts], + ) + const tagRows = await tx.getAll('SELECT name FROM TaskTag WHERE taskId = ?', [String(s.id)]) + for (const t of tagRows) { + await tx.execute('INSERT INTO TaskTag (id, uuid, taskId, name) VALUES (?, ?, ?, ?)', [uuid(), uuid(), newId, t.name]) + } + const keptMembers = (await tx.getAll('SELECT userId FROM TaskMember WHERE taskId = ?', [String(s.id)])).map((r) => String(r.userId)).filter((x) => targetMemberIds.has(x)) + for (const m of keptMembers) { + await tx.execute('INSERT INTO TaskMember (id, uuid, taskId, userId) VALUES (?, ?, ?, ?)', [uuid(), uuid(), newId, m]) + } + return newId + } + + const newId = await db().writeTransaction(async (tx) => { + const pid = await copyOne(tx, source, null) + const children = await tx.getAll('SELECT * FROM Task WHERE parentId = ?', [String(source.id)]) + for (const child of children) await copyOne(tx, child, pid) + return pid + }) + const row = await db().get('SELECT * FROM Task WHERE id = ?', [newId]) + const shape = await shapeTasks([row], targetColumns) + return { item: shape(row), dropped: { assignee: null, members: [] } } + }, + // ---------- 归档 (T-ARC, 本地实现: 写本地库 -> 上行) ---------- + + /** 手动归档: 连带全部子任务, 幂等 (T-ARC-01) */ + archive: async (id) => { + const task = await db().getOptional('SELECT * FROM Task WHERE id = ?', [String(id)]) + if (!task) throw new Error('任务不存在或无权限') + const now = now() + await db().writeTransaction(async (tx) => { + const kids = await tx.getAll('SELECT id FROM Task WHERE parentId = ?', [String(task.id)]) + const ids = [String(task.id), ...kids.map((k) => String(k.id))] + const ph = ids.map(() => '?').join(',') + await tx.execute( + `UPDATE Task SET archived = 1, archivedAt = ?, archiveSource = 'manual', updatedAt = ? WHERE id IN (${ph})`, + [now, now, ...ids], + ) + }) + return { ok: true, count: 1 } + }, + + /** 恢复归档: 回原列, 状态不变, 连带子任务 (T-ARC-03) */ + unarchive: async (id) => { + const task = await db().getOptional('SELECT * FROM Task WHERE id = ?', [String(id)]) + if (!task) throw new Error('任务不存在或无权限') + const now = now() + await db().writeTransaction(async (tx) => { + const kids = await tx.getAll('SELECT id FROM Task WHERE parentId = ?', [String(task.id)]) + const ids = [String(task.id), ...kids.map((k) => String(k.id))] + const ph = ids.map(() => '?').join(',') + await tx.execute( + `UPDATE Task SET archived = 0, archivedAt = NULL, archiveSource = NULL, updatedAt = ? WHERE id IN (${ph})`, + [now, ...ids], + ) + }) + return { ok: true, count: 1 } + }, + + /** + * 已归档查询: 关键词 / 类型 / 列 / 归档日期区间 + 游标分页 (T-ARC-04) + * 参数: { keyword, kind, columnId, from, to, limit, cursor } + * cursor 形如 "|", 按 (archivedAt desc, id desc) 取下一页 + */ + archivedList: async (projectId, params = {}) => { + const columns = await getColumns(projectId) + const p = params || {} + const limit = Math.min(100, Math.max(1, Number(p.limit) || 20)) + const where = ['projectId = ?', 'archived = 1', 'parentId IS NULL'] + const args = [String(projectId)] + if (p.keyword) { + where.push('title LIKE ?') + args.push(`%${String(p.keyword).trim()}%`) + } + if (p.kind) { + where.push('kind = ?') + args.push(String(p.kind)) + } + if (p.columnId) { + where.push('columnId = ?') + args.push(String(p.columnId)) + } + if (p.from) { + where.push('archivedAt >= ?') + args.push(String(p.from) + 'T00:00:00+08:00'.replace('+08:00', 'Z')) + } + if (p.to) { + where.push('archivedAt <= ?') + args.push(String(p.to) + 'T23:59:59.999Z') + } + if (p.cursor) { + const [ts, cid] = String(p.cursor).split('|') + where.push('(archivedAt < ? OR (archivedAt = ? AND id < ?))') + args.push(ts, ts, String(cid)) + } + const rows = await db().getAll( + `SELECT * FROM Task WHERE ${where.join(' AND ')} ORDER BY archivedAt DESC, id DESC LIMIT ${limit + 1}`, + args, + ) + const hasMore = rows.length > limit + const page = rows.slice(0, limit) + const shape = await shapeTasks(page, columns) + const items = page.map(shape) + const last = page[page.length - 1] + const nextCursor = hasMore && last ? `${last.archivedAt}|${last.id}` : null + return { items, nextCursor } + }, +} diff --git a/src/auth/AuthContext.jsx b/src/auth/AuthContext.jsx index 1aeb886..fe57e08 100644 --- a/src/auth/AuthContext.jsx +++ b/src/auth/AuthContext.jsx @@ -1,5 +1,6 @@ import { createContext, useContext, useEffect, useState } from 'react' -import { api, getToken, setToken, setRememberFlag } from '../api/client.js' +import { api, getToken, setToken, setRememberFlag, setCurrentUserId, clearApiCache } from '../api/client.js' +import { disconnectAndClearPowersync } from '../lib/powersync/db.js' const AuthContext = createContext(null) @@ -17,10 +18,14 @@ export function AuthProvider({ children }) { } try { const data = await api('/auth/me') - if (!cancelled) setUser(data.user) + if (!cancelled) { + setUser(data.user) + setCurrentUserId(data.user?.id) + } } catch { if (!cancelled) { setToken('') + setCurrentUserId('') setUser(null) } } finally { @@ -36,6 +41,7 @@ export function AuthProvider({ children }) { setToken(data.token) setRememberFlag(!!remember) setUser(data.user) + setCurrentUserId(data.user?.id) return data.user } @@ -44,6 +50,11 @@ export function AuthProvider({ children }) { setToken('') setRememberFlag(false) setUser(null) + setCurrentUserId('') + try { clearApiCache() } catch { /* ignore */ } + try { localStorage.removeItem('iboard_ps_cred') } catch { /* ignore */ } + // 断开同步并清空本地 SQLite (0.2.1 离线编辑) + try { await disconnectAndClearPowersync() } catch { /* ignore */ } } return ( diff --git a/src/components/BottomNav.jsx b/src/components/BottomNav.jsx index 52b1606..ab2e550 100644 --- a/src/components/BottomNav.jsx +++ b/src/components/BottomNav.jsx @@ -4,23 +4,25 @@ import TabIcon from './TabIcon.jsx' // 底部 Tab 配置: key 既是路由前缀, 又是 icon 名 // 使用 antd-mobile TabBar: 自带 safe-area 处理, 仿原生 iOS/Android 风格 +// V0.2: 打卡置最左并为默认 Tab; 原「首页」的报警/新闻并入「宏观」, 不再有首页 Tab +// V0.2 任务模块: 「任务」插入到「打卡」之后 (T-PM-06), 默认首页仍为打卡 const TABS = [ - { key: '/', name: 'home', label: '首页' }, + { key: '/checkin', name: 'checkin', label: '打卡' }, + { key: '/task', name: 'task', label: '任务' }, { key: '/macro', name: 'macro', label: '宏观' }, { key: '/monitor', name: 'monitor', label: '监控' }, { key: '/me', name: 'me', label: '我' }, ] -// 根据当前路径计算激活的 tab key (精确匹配 '/' , 其他前缀匹配) +// 根据当前路径计算激活的 tab key (精确匹配 + 前缀匹配) function activeKeyFromPath(pathname) { for (const t of TABS) { - if (t.key === '/') { - if (pathname === '/') return t.key - } else if (pathname === t.key || pathname.startsWith(t.key + '/')) { + if (pathname === t.key || pathname.startsWith(t.key + '/')) { return t.key } } - return '/' + // 未知路径兜底到第一个 Tab (打卡) + return TABS[0].key } // 移动端底部 Tab 导航: antd-mobile TabBar, 内置安全区 @@ -46,4 +48,4 @@ export default function BottomNav() { ))} ) -} +} diff --git a/src/components/PageHeader.jsx b/src/components/PageHeader.jsx new file mode 100644 index 0000000..8c56b75 --- /dev/null +++ b/src/components/PageHeader.jsx @@ -0,0 +1,12 @@ +// 四个主 Tab 的统一头部 (V0.2.1 界面调整) +// - 高度为原 antd-mobile NavBar 的一半 (23px) +// - 标题小字 + 主色, 仅作定位用, 不抢内容视觉 +// - 「我」页不使用该头部 (页面顶部直接是头像区), 因此本组件只被 +// 打卡 / 宏观 / 监控 三页引用, 互不影响 +export default function PageHeader({ title }) { + return ( +
+ {title} +
+ ) +} diff --git a/src/components/RingProgress.jsx b/src/components/RingProgress.jsx new file mode 100644 index 0000000..db10872 --- /dev/null +++ b/src/components/RingProgress.jsx @@ -0,0 +1,120 @@ +import { useEffect, useRef, useState } from 'react' + +// 环形进度组件 (F-CK-03/04/05, U-01) +// props: +// percent: 0~1 完成度 +// size: 外框尺寸(px) +// stroke: 环宽(px) +// color: 进度色 +// completed: 是否达标 (达标时显示对勾而非数字) +// done/target: 中心文案 +// onClick: 可选, 点击整个环触发 (用于"点环即打卡") +export default function RingProgress({ + percent = 0, + size = 58, + stroke = 6, + color = '#1677ff', + completed = false, + done = 0, + target = 1, + over = 0, + children, + onClick, + disabled = false, +}) { + const p = Math.max(0, Math.min(1, Number(percent) || 0)) + const r = (size - stroke) / 2 + const c = 2 * Math.PI * r + const offset = c * (1 - p) + + return ( +
+ + {/* 背景环 */} + + {/* 进度环: 从 12 点方向顺时针 */} + + +
+ {children !== undefined ? children : ( + completed ? ( + + + + ) : ( + + {done} + /{target} + + ) + )} +
+ {over > 0 && +{over}} +
+ ) +} + +// 完成特效 (F-CK-07, U-02/U-03/U-07) +// props: fire 计数器(每次 +1 触发一次), color 主色 +// V0.2 设计变更: 去掉特效开关, 达标特效**恒开** (没有任何关闭入口) +export function Celebrate({ fire = 0, color = '#1677ff' }) { + const [burst, setBurst] = useState(0) + const [visible, setVisible] = useState(false) + const prev = useRef(fire) + const timer = useRef(null) + + useEffect(() => { + if (fire === prev.current) return + prev.current = fire + if (fire <= 0) return + setBurst((n) => n + 1) + setVisible(true) + haptic() + clearTimeout(timer.current) + timer.current = setTimeout(() => setVisible(false), 900) + return () => clearTimeout(timer.current) + }, [fire]) + + if (!visible) return null + const dots = Array.from({ length: 14 }, (_, i) => i) + return ( +
+ + {dots.map((i) => { + const angle = (360 / dots.length) * i + (i % 2 ? 8 : -8) + const dist = 26 + (i % 4) * 8 + return ( + + ) + })} +
+ ) +} + +// 轻量触感反馈 (Web Vibration API; Tauri Android WebView 支持) +export function haptic(ms = 18) { + try { + if (typeof navigator !== 'undefined' && navigator.vibrate) navigator.vibrate(ms) + } catch { /* ignore */ } +} diff --git a/src/components/SwipeTabs.jsx b/src/components/SwipeTabs.jsx index 5aa1b55..e885b2b 100644 --- a/src/components/SwipeTabs.jsx +++ b/src/components/SwipeTabs.jsx @@ -1,20 +1,83 @@ +import { useCallback, useEffect, useRef, useState } from 'react' import { Tabs } from 'antd-mobile' // 顶部分类 + 下方仅渲染当前激活 tab 的内容 // - 顶部: antd-mobile Tabs (点击切换) // - 下方: 同时挂载所有 tab, 通过 [hidden] 仅显示激活的那个 (保留各 tab 内部状态) +// - 右侧溢出提示: 当 tab 条还能向右滚动时, 右边缘显示渐变 + 箭头, +// 提示"右侧还有更多 tab"; 滚动到末尾后自动消失 // props: // tabs: [{ key, label, render }] // active: 当前激活 key // onChange(key): 切换回调 export default function SwipeTabs({ tabs, active, onChange }) { + const wrapRef = useRef(null) + const [more, setMore] = useState(false) + const [headerH, setHeaderH] = useState(45) + + // 判断右侧是否还有未展示的 tab (顺带记录 tab 条高度, 让提示层只覆盖页签行) + const measure = useCallback(() => { + const wrap = wrapRef.current + if (!wrap) return + const list = wrap.querySelector('.adm-tabs-tab-list') + const header = wrap.querySelector('.adm-tabs-header') + if (header && header.offsetHeight > 0) setHeaderH(header.offsetHeight) + if (!list) return + setMore(list.scrollLeft + list.clientWidth < list.scrollWidth - 2) + }, []) + + useEffect(() => { + const wrap = wrapRef.current + if (!wrap) return + const list = wrap.querySelector('.adm-tabs-tab-list') + const header = wrap.querySelector('.adm-tabs-header') + if (!list) return + + // 首次布局未稳定 (字体/宽度), 下一帧 + 300ms 各补测一次 + const raf = requestAnimationFrame(measure) + const timer = setTimeout(measure, 300) + list.addEventListener('scroll', measure, { passive: true }) + const ro = new ResizeObserver(measure) + ro.observe(list) + if (header) ro.observe(header) + return () => { + cancelAnimationFrame(raf) + clearTimeout(timer) + list.removeEventListener('scroll', measure) + ro.disconnect() + } + }, [measure, tabs.length]) + + // 切换 tab 后把激活项滚到可见区域中间, 避免选中项被藏在屏幕外 + useEffect(() => { + const wrap = wrapRef.current + if (!wrap) return + const list = wrap.querySelector('.adm-tabs-tab-list') + const activeEl = wrap.querySelector('.adm-tabs-tab-active') + if (!list || !activeEl) return + const target = activeEl.offsetLeft - (list.clientWidth - activeEl.offsetWidth) / 2 + list.scrollTo({ left: Math.max(0, target), behavior: 'smooth' }) + }, [active, tabs.length]) + return ( -
+
{tabs.map((t) => ( ))} + {more && ( +
{ + const list = wrapRef.current?.querySelector('.adm-tabs-tab-list') + if (list) list.scrollTo({ left: list.scrollLeft + list.clientWidth * 0.6, behavior: 'smooth' }) + }} + > + › +
+ )}
{tabs.map((t) => (
) -} +} diff --git a/src/components/TabIcon.jsx b/src/components/TabIcon.jsx index 51c00d1..d361be2 100644 --- a/src/components/TabIcon.jsx +++ b/src/components/TabIcon.jsx @@ -2,13 +2,7 @@ // props: name 字符串, active 布尔 // 返回对应的 24x24 SVG 节点 const ICONS = { - home: (active) => ( - - - - {active ? : null} - - ), + // V0.2: 打卡移到最左, 原「首页」已并入宏观, home 图标不再使用 macro: (active) => ( @@ -22,6 +16,25 @@ const ICONS = { ), + // 打卡: 日历 + 对勾 + checkin: (active) => ( + + + + + + + ), + // 任务: 清单本 + 勾选行 (V0.2 任务模块) + task: (active) => ( + + + + + + + + ), me: (active) => ( @@ -34,4 +47,4 @@ export default function TabIcon({ name, active }) { const render = ICONS[name] if (!render) return null return {render(!!active)} -} +} diff --git a/src/components/checkin/useHabitDrag.js b/src/components/checkin/useHabitDrag.js new file mode 100644 index 0000000..e21a1b2 --- /dev/null +++ b/src/components/checkin/useHabitDrag.js @@ -0,0 +1,163 @@ +import { useCallback, useEffect, useRef, useState } from 'react' + +// 打卡项组内拖动排序 (MergedView 分组列表) +// 触发路径: +// 1. 把手 (bindHandle, 推荐): 卡片左侧 ⠿ 把手 touch-action:none + +// 非 passive touchstart preventDefault —— 浏览器按规范完全不接管该 +// 触摸, 指针流不会被滚动抢占取消, Android WebView 上可靠; +// 2. 长按 (bind): 长按 400ms 激活。pan-y 下列表可滚时手指纵向抖动会 +// 触发滚动接管 (pointercancel), 仅作补充入口。 +// 拖动: 激活时克隆原卡片作为跟手浮层 (淡化), 原位保留虚线占位; +// elementFromPoint 命中同组 [data-habit-row] 作为落点 (data-habit-period +// 必须一致, 即只在 每天/每周/每月 组内排序), 按指针在目标卡上半/下半 +// 决定插到其前/后, 松手提交。 +export default function useHabitDrag({ onDrop }) { + const [dragItem, setDragItem] = useState(null) // 拖动中的打卡项 (视觉态) + const [over, setOver] = useState(null) // { id, after } 落点 + const st = useRef({ timer: null, active: false, startX: 0, startY: 0, item: null, over: null, srcEl: null, ghost: null }) + const lastEnd = useRef(0) + + // onDrop 取"最新一次渲染"的闭包: bind/bindHandle 是空依赖 useCallback, + // 若直接捕获 onDrop, 会固化首次渲染(数据未加载, groups=[])的落位函数, + // 松手时静默早退。用 ref 桥接到实时实现。 + const onDropRef = useRef(onDrop) + onDropRef.current = onDrop + + // 跟手浮层: 激活时克隆原卡片挂到 body (脱离页签滑动容器的 transform), + // 移动时直接改 transform, 不经 React 渲染 + const moveGhost = (x, y) => { + const g = st.current.ghost + if (g) g.style.transform = `translate(${x}px, ${y}px) translate(-50%, -60%)` + } + const mountGhost = (x, y) => { + const src = st.current.srcEl + if (!src) return + const clone = src.cloneNode(true) + clone.classList.remove('dragging', 'dragover', 'after', 'before') + clone.style.width = `${src.offsetWidth}px` + const g = document.createElement('div') + g.className = 'drag-ghost' + g.appendChild(clone) + document.body.appendChild(g) + st.current.ghost = g + moveGhost(x, y) + } + + const cleanup = useCallback(() => { + if (st.current.timer) { clearTimeout(st.current.timer); st.current.timer = null } + st.current.active = false + st.current.over = null + st.current.srcEl = null + if (st.current.ghost) { st.current.ghost.remove(); st.current.ghost = null } + setDragItem(null) + setOver(null) + }, []) + + useEffect(() => () => cleanup(), [cleanup]) + + /** 拖动刚结束 (350ms 内): 抑制松手合成的 click 误触打卡 */ + const justEnded = useCallback(() => Date.now() - lastEnd.current < 350, []) + + const hitRow = (x, y) => { + const el = document.elementFromPoint(x, y) + const rowEl = el?.closest?.('[data-habit-row]') + if (!rowEl) return null + const r = rowEl.getBoundingClientRect() + const id = Number(rowEl.dataset.habitId) + if (id === st.current.item.id) return null + // 允许跨组: 落到其他周期组的目标卡上 = 同时改周期 + 落位 (由 onDrop 处理) + const period = rowEl.dataset.habitPeriod + return { id, period, after: y > r.top + r.height / 2 } + } + + const onMove = (ev) => { + const s = st.current + if (!s.active) { + if (s.timer && (Math.abs(ev.clientX - s.startX) > 10 || Math.abs(ev.clientY - s.startY) > 10)) { + clearTimeout(s.timer); s.timer = null + } + return + } + ev.preventDefault() + moveGhost(ev.clientX, ev.clientY) + const hit = hitRow(ev.clientX, ev.clientY) + if (hit) { s.over = hit; setOver(hit) } + else if (s.over) { s.over = null; setOver(null) } + } + + const onUp = () => { + const s = st.current + if (s.active && s.item) { + lastEnd.current = Date.now() + if (s.over && s.over.id !== s.item.id) onDropRef.current?.(s.item, s.over.id, s.over.after, s.over.period) + } + cleanup() + } + + // 指针监听装配: instant=true 时按下即激活 (把手路径), 否则长按 400ms 激活 + const armListeners = (ev, instant, item) => { + try { ev.currentTarget.setPointerCapture(ev.pointerId) } catch { /* ignore */ } + const el = ev.currentTarget + const move = (e2) => onMove(e2) + const tm = (e2) => { if (st.current.active) e2.preventDefault() } + const ts = (e2) => e2.preventDefault() + const up = () => { onUp(); detach() } + const cancel = () => { + if (st.current.active) lastEnd.current = Date.now() + cleanup() + detach() + } + const detach = () => { + el.removeEventListener('pointermove', move) + el.removeEventListener('pointerup', up) + el.removeEventListener('pointercancel', cancel) + el.removeEventListener('touchmove', tm) + el.removeEventListener('touchstart', ts) + } + el.addEventListener('pointermove', move) + el.addEventListener('pointerup', up) + el.addEventListener('pointercancel', cancel) + el.addEventListener('touchmove', tm, { passive: false }) + if (instant) el.addEventListener('touchstart', ts, { passive: false }) + + const activate = () => { + st.current.active = true + setDragItem(item) + mountGhost(st.current.startX, st.current.startY) + if (navigator.vibrate) navigator.vibrate(15) + } + if (instant) activate() + else st.current.timer = setTimeout(activate, 400) + } + + /** 绑定到卡片: 卡片需 touch-action: pan-y (长按路径) */ + const bind = useCallback((item) => ({ + onPointerDown: (ev) => { + if (ev.button !== undefined && ev.button !== 0) return + st.current = { + ...st.current, startX: ev.clientX, startY: ev.clientY, item, active: false, over: null, + srcEl: ev.currentTarget.closest('[data-habit-row]'), + } + armListeners(ev, false, item) + }, + onContextMenu: (ev) => ev.preventDefault(), + // eslint-disable-next-line react-hooks/exhaustive-deps + }), []) + + /** 绑定到拖动把手: 把手需 touch-action: none, 按下即拖 */ + const bindHandle = useCallback((item) => ({ + onPointerDown: (ev) => { + if (ev.button !== undefined && ev.button !== 0) return + ev.stopPropagation() // 不触发卡片自身的长按路径 + st.current = { + ...st.current, startX: ev.clientX, startY: ev.clientY, item, active: false, over: null, + srcEl: ev.currentTarget.closest('[data-habit-row]'), + } + armListeners(ev, true, item) + }, + onContextMenu: (ev) => ev.preventDefault(), + // eslint-disable-next-line react-hooks/exhaustive-deps + }), []) + + return { dragItem, over, bind, bindHandle, cancel: cleanup, justEnded } +} diff --git a/src/components/task/Avatar.jsx b/src/components/task/Avatar.jsx new file mode 100644 index 0000000..8575fcd --- /dev/null +++ b/src/components/task/Avatar.jsx @@ -0,0 +1,22 @@ +// 负责人头像: 姓氏首字 + 按名字稳定的底色; 未指派灰色圆点 +const COLORS = ['#1677ff', '#00b578', '#ff8f1f', '#a855f7', '#14b8a6', '#f43f9e', '#5a6a80'] + +export default function Avatar({ name, size = 18 }) { + const seed = [...(name || '')].reduce((a, ch) => a + ch.charCodeAt(0), 0) + const color = COLORS[seed % COLORS.length] + return ( + + {name ? name.slice(0, 1) : '·'} + + ) +} diff --git a/src/components/task/CopyTaskPopup.jsx b/src/components/task/CopyTaskPopup.jsx new file mode 100644 index 0000000..a3ea884 --- /dev/null +++ b/src/components/task/CopyTaskPopup.jsx @@ -0,0 +1,61 @@ +import { useEffect, useState } from 'react' +import { DotLoading, Popup, Toast } from 'antd-mobile' +import { projectApi, taskApi } from '../../api/task.js' + +// 跨项目拷贝弹层 (T-CP-*): 选择目标项目 → 深拷贝; 人员不属于目标项目时丢弃并提示 +export default function CopyTaskPopup({ task, visible, onClose, onDone }) { + const [projects, setProjects] = useState(null) + + useEffect(() => { + if (!visible) return + setProjects(null) + projectApi.list() + .then((d) => setProjects((d.items || []).filter((p) => p.id !== task.projectId && !p.archived))) + .catch((e) => { + Toast.show({ content: e.message }) + setProjects([]) + }) + }, [visible, task.projectId]) + + async function doCopy(projectId, name) { + try { + const r = await taskApi.copy(task.id, projectId) + const dropped = r.dropped || {} + const lost = [dropped.assignee?.name, ...(dropped.members || []).map((m) => m.name)].filter(Boolean) + Toast.show({ + content: lost.length + ? `已拷贝到「${name}」;${lost.join('、')} 不是该项目成员,已不保留` + : `已拷贝到「${name}」`, + }) + onClose?.() + onDone?.() + } catch (e) { + Toast.show({ content: e.message }) + } + } + + return ( + +
跨项目拷贝
+
「{task.title}」→ 选择目标项目;源任务保持不变
+ + {!projects &&
加载项目…
} + {projects && projects.length === 0 &&
没有其他可拷贝的项目
} + {(projects || []).map((p) => ( +
doCopy(p.id, p.name)}> +
{(p.name || '?').slice(0, 1)}
+ {p.name} + 拷贝 +
+ ))} +
取消
+
+ ) +} diff --git a/src/components/task/RecentProjects.jsx b/src/components/task/RecentProjects.jsx new file mode 100644 index 0000000..09ad746 --- /dev/null +++ b/src/components/task/RecentProjects.jsx @@ -0,0 +1,146 @@ +import { useEffect, useMemo, useRef, useState } from 'react' +import { getRecentIds, getRecentOrder, setRecentOrder } from '../../pages/task/prefs.js' +import { colorOfProject } from '../../pages/task/projectIcon.js' + +// 首屏最近项目条 (T-VW-08/10) +// - 展示最近使用的 3 个项目 + 「更多项目」入口 +// - 顺序: 手动排序(recentOrder)优先, 其次最近访问(recentIds), 兜底列表顺序 +// - 长按卡片 300ms 进入拖拽态, 水平拖动实时换位, 松手持久化(设备端) +const LONG_PRESS = 300 +const MOVE_TOLERANCE = 8 + +export default function RecentProjects({ projects, currentId, onSelect, onMore }) { + const [ver, setVer] = useState(0) // 手动排序后强制重算 + const [dragIdx, setDragIdx] = useState(-1) // 拖拽中的卡片下标 (视觉态) + const dragIdxRef = useRef(-1) // 监听器闭包读的下标 (state 在闭包里是旧值) + const barRef = useRef(null) + const timer = useRef(null) + const start = useRef(null) + const active = useRef(false) + + // 展示顺序: 手动 > 自动 > 列表序, 只取未归档的前 3 + const shown = useMemo(() => { + if (!projects) return [] + const score = new Map() + getRecentOrder().forEach((id, i) => score.set(id, i)) + let n = score.size + getRecentIds().forEach((id) => { if (!score.has(id)) score.set(id, n++) }) + const fallback = n + 1000 + return [...projects] + .filter((p) => !p.archived) + .sort((a, b) => (score.get(a.id) ?? fallback) - (score.get(b.id) ?? fallback)) + .slice(0, 3) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [projects, ver]) + + useEffect(() => () => cleanup(), []) + + function cleanup() { + if (timer.current) { clearTimeout(timer.current); timer.current = null } + active.current = false + start.current = null + document.removeEventListener('touchmove', onMove, { passive: false }) + document.removeEventListener('touchend', onEnd) + document.removeEventListener('mousemove', onMove) + document.removeEventListener('mouseup', onEnd) + } + + const pointOf = (ev) => { + const t = ev.touches?.[0] || ev.changedTouches?.[0] || ev + return { x: t.clientX, y: t.clientY } + } + + // 按横坐标计算落点下标 (卡片宽 96 + 间距 8) + function indexAt(x, fallback = -1) { + const rect = barRef.current?.getBoundingClientRect() + if (!rect) return fallback + const rel = x - rect.left + 4 + return Math.max(0, Math.min(shown.length - 1, Math.floor(rel / 104))) + } + + function onMove(ev) { + const p = pointOf(ev) + if (!active.current) { + // 未激活前: 移动超容差则取消长按 (用户想滚动) + if (start.current && (Math.abs(p.x - start.current.x) > MOVE_TOLERANCE || Math.abs(p.y - start.current.y) > MOVE_TOLERANCE)) { + if (timer.current) { clearTimeout(timer.current); timer.current = null } + } + return + } + if (ev.cancelable) ev.preventDefault() + // 用 ref 取当前拖拽下标: 监听器闭包里的 state 是旧值 + const from = dragIdxRef.current + const to = indexAt(p.x, from) + if (to !== from && to >= 0 && from >= 0) { + // 实时换位: 重写设备端顺序, ver 触发重算 (shown 由 memo 派生) + const ids = shown.map((p2) => p2.id) + const [moved] = ids.splice(from, 1) + ids.splice(to, 0, moved) + setRecentOrder(ids) + dragIdxRef.current = to + setDragIdx(to) + setVer((v) => v + 1) + } + } + + function onEnd() { + if (active.current) setVer((v) => v + 1) + cleanup() + dragIdxRef.current = -1 + setDragIdx(-1) + } + + function beginPress(i, ev) { + if (ev.button && ev.button !== 0) return + const p = pointOf(ev) + start.current = p + active.current = false + cleanup() + dragIdxRef.current = i + timer.current = setTimeout(() => { + active.current = true + setDragIdx(i) + if (navigator.vibrate) navigator.vibrate(10) + }, LONG_PRESS) + document.addEventListener('touchmove', onMove, { passive: false }) + document.addEventListener('touchend', onEnd) + document.addEventListener('mousemove', onMove) + document.addEventListener('mouseup', onEnd) + } + + if (!projects) return null + + return ( + <> +
+ {shown.map((p, i) => ( +
beginPress(i, ev)} + onMouseDown={(ev) => beginPress(i, ev)} + onClick={() => { if (!active.current && dragIdx < 0) onSelect?.(p.id) }} + > +
+ {p.icon ? '' : (p.name || '?').slice(0, 1)} +
+
{p.name}
+
{p.taskCount ?? 0} 个任务
+
⠿
+
+ ))} +
+
▤
+ 更多项目 +
+
+
长按卡片可调整顺序(本机记忆)
+ + ) +} diff --git a/src/components/task/StatusDot.jsx b/src/components/task/StatusDot.jsx new file mode 100644 index 0000000..200cc9a --- /dev/null +++ b/src/components/task/StatusDot.jsx @@ -0,0 +1,8 @@ +// 任务状态小圆点 / 药丸标记 (T-ST-09) +const LABEL = { todo: '未开始', doing: '执行中', done: '完成' } + +export default function StatusDot({ status = 'todo', pill = false, className = '' }) { + const cls = `sdot ${status}${pill ? ' pill' : ''} ${className}`.trim() + if (pill) return {LABEL[status] || ''} + return +} diff --git a/src/components/task/TaskCard.jsx b/src/components/task/TaskCard.jsx new file mode 100644 index 0000000..9f3a781 --- /dev/null +++ b/src/components/task/TaskCard.jsx @@ -0,0 +1,83 @@ +import StatusDot from './StatusDot.jsx' +import Avatar from './Avatar.jsx' +import { kindColor } from '../../pages/task/kind.js' + +// 看板卡片 (T-UI-02): 轻量化 +// - 标题 + 状态点 (T-ST-09) +// - 负责人 12px/600 突出 (T-AS-01); 任务成员 10.5px 弱化置于下方 (T-AS-02) +// - 子任务内嵌简洁展示 + 可直接勾选 (T-ST-06/07) +const MAX_SUB = 3 + +export default function TaskCard({ + task, codePrefix, bindDrag, onClick, onToggleSub, onToggle, onMore, dragging = false, highlight = false, +}) { + const children = task.children || [] + const shown = children.slice(0, MAX_SUB) + const rest = children.length - shown.length + + return ( +
+ {/* 左侧竖排: 状态点 + ⋯ */} +
+ { e.stopPropagation(); onToggle?.(task) }} + onPointerDown={(e) => e.stopPropagation()} + title="点按切换完成" + > + + + {onMore && ( + { e.stopPropagation(); onMore(task) }} + onPointerDown={(e) => e.stopPropagation()} + >⋯ + )} +
+ +
+
+ {task.title} +
+ +
+ {codePrefix && task.number != null && ( + {codePrefix}-{task.number} + )} + {task.markDate && ( + {task.markDate.slice(5, 10).replace('-', '/')} + )} + {task.kind && ( + {task.kind} + )} + + + {task.members?.length > 0 && ( + + {task.members.map((m) => m.name).join('、')} + )} + +
+ + {shown.map((c) => ( +
{ e.stopPropagation(); onToggleSub?.(c) }} + onPointerDown={(e) => e.stopPropagation()} + > + + {c.title} + +
+ ))} + {rest > 0 &&
+{rest} 个子任务
} +
+
+ ) +} diff --git a/src/components/task/TaskRow.jsx b/src/components/task/TaskRow.jsx new file mode 100644 index 0000000..9bddf99 --- /dev/null +++ b/src/components/task/TaskRow.jsx @@ -0,0 +1,83 @@ +import StatusDot from './StatusDot.jsx' +import Avatar from './Avatar.jsx' +import { kindColor } from '../../pages/task/kind.js' + +const statusLabel = (s) => (s === 'done' ? '完成' : s === 'doing' ? '执行中' : '未开始') + +// 列表视图的一行 (Todoist 风格, T-UI-01) +// 点击行体 → 打开详情; 长按 400ms 后左右拖动换列 (bindDrag 来自 useRowDrag) +// 子任务与普通任务同构: 标题一行 + 状态元信息一行 (字体略小), 不显示分组标签 +// 复选框 / ⋯ 是独立控件: pointerdown 不冒泡, 不发起拖动, +// 避免长按行后抬手时合成的 click 落在控件上误触状态切换 +export default function TaskRow({ + task, codePrefix, onToggle, onClick, onMore, bindDrag, bindDragHandle, dragging = false, dragOver, indent = false, children, +}) { + const done = task.status === 'done' + return ( + <> +
+ {!indent && bindDragHandle && ( + ⠿ + )} +
{ e.stopPropagation(); onToggle?.(task) }} + onPointerDown={(e) => e.stopPropagation()} + > + + + +
+ + {indent ? ( +
+ {task.title} +
+ + {statusLabel(task.status)} +
+
+ ) : ( + <> +
+
{task.title}
+ {/* 无类型/成员也保留该行, 与有标签的行等高 */} +
+ {codePrefix && task.number != null && ( + {codePrefix}-{task.number} + )} + {task.markDate && ( + {task.markDate.slice(5, 10).replace('-', '/')} + )} + {task.kind && ( + {task.kind} + )} + {task.members?.length > 0 && ( + + {task.members.map((m) => m.name).join('、')} + )} +
+
+
+ { e.stopPropagation(); onMore?.(task) }} + onPointerDown={(e) => e.stopPropagation()} + >⋯ + +
+ + )} +
+ {children} + + ) +} diff --git a/src/components/task/UserPicker.jsx b/src/components/task/UserPicker.jsx new file mode 100644 index 0000000..7eb789d --- /dev/null +++ b/src/components/task/UserPicker.jsx @@ -0,0 +1,66 @@ +import { useEffect, useState } from 'react' +import { Popup, Toast } from 'antd-mobile' + +// 人员选择 (T-AS-03/04): 只能从"当前项目成员"中选 +export default function UserPicker({ + visible, members = [], value, multiple = false, title = '选择负责人', + allowEmpty = true, onClose, onConfirm, +}) { + const [sel, setSel] = useState(multiple ? [] : null) + + useEffect(() => { + if (!visible) return + setSel(multiple ? (value || []) : (value ?? null)) + }, [visible, value, multiple]) + + const toggle = (uid) => { + if (multiple) { + setSel((prev) => { + const arr = Array.isArray(prev) ? prev : [] + return arr.includes(uid) ? arr.filter((x) => x !== uid) : [...arr, uid] + }) + } else { + setSel((prev) => (prev === uid ? null : uid)) + } + } + + const confirm = () => { + if (!multiple && sel === null && !allowEmpty) { + Toast.show({ content: '请选择负责人' }) + return + } + onConfirm?.(sel) + onClose?.() + } + + return ( + +
{title}
+
仅可选择当前项目的成员
+ + {members.length === 0 &&
项目暂无成员
} + + {members.map((m) => { + const on = multiple ? (Array.isArray(sel) && sel.includes(m.userId)) : sel === m.userId + return ( +
toggle(m.userId)}> +
{(m.name || '?').slice(0, 1)}
+ {m.name} + +
+ ) + })} + + {allowEmpty && !multiple && ( +
setSel(null)}> +
—
+ 清除负责人 + +
+ )} + +
确定
+
取消
+
+ ) +} diff --git a/src/components/task/useColumnDrag.js b/src/components/task/useColumnDrag.js new file mode 100644 index 0000000..58d477f --- /dev/null +++ b/src/components/task/useColumnDrag.js @@ -0,0 +1,180 @@ +import { useCallback, useEffect, useRef, useState } from 'react' + +// 看板卡片列拖动 (重点设计 §7.1) +// 方案要点: +// 1. Pointer Events + setPointerCapture —— 触摸/鼠标统一, 捕获后不丢事件 +// 2. 长按 400ms 激活 (激活前移动 >10px 视为滚动, 取消本次拖动) +// 3. 激活后卡片原位抬起 (不跟随手指, 无浮层/无任务名) +// 4. 拖动中持续 elementFromPoint 命中 [data-column-id] 高亮目标列; +// 指针靠近容器左右边缘时 rAF 自动横滚, 可拖到屏幕外的列 +// 5. 松手落到目标列 (≠原列) 才提交; pointercancel (浏览器接管手势, +// 如触发纵向滚动) 只放弃不提交 +// 6. 防误触: 卡片禁长按系统菜单(contextmenu); 拖动结束后 350ms 内的 +// click 视为拖动余波, 不打开详情 (justEnded) +export default function useColumnDrag({ onDrop, scrollSelector = '.board-wrap' } = {}) { + const [dragTask, setDragTask] = useState(null) // 拖动中的任务 (视觉态) + const [overCol, setOverCol] = useState(null) // 高亮的目标列 id + const st = useRef({ timer: null, active: false, startX: 0, startY: 0, task: null, autoScroll: 0, raf: 0, srcEl: null, ghost: null }) + const lastEnd = useRef(0) // 拖动结束时刻 + + // onDrop 经 ref 桥接实时闭包 (bind 空依赖 useCallback, 防陈旧) + const onDropRef = useRef(onDrop) + onDropRef.current = onDrop + + // 跟手浮层: 激活时克隆原卡片挂到 body, 移动时直接改 transform + const moveGhost = (x, y) => { + const g = st.current.ghost + if (g) g.style.transform = `translate(${x}px, ${y}px) translate(-50%, -60%)` + } + const mountGhost = (x, y) => { + const src = st.current.srcEl + if (!src) return + const clone = src.cloneNode(true) + clone.classList.remove('dragging', 'hl') + clone.style.width = `${src.offsetWidth}px` + const g = document.createElement('div') + g.className = 'drag-ghost' + g.appendChild(clone) + document.body.appendChild(g) + st.current.ghost = g + moveGhost(x, y) + } + + const stopAutoScroll = () => { + st.current.autoScroll = 0 + if (st.current.raf) { cancelAnimationFrame(st.current.raf); st.current.raf = 0 } + } + + const cleanup = useCallback(() => { + if (st.current.timer) { clearTimeout(st.current.timer); st.current.timer = null } + st.current.active = false + st.current.srcEl = null + if (st.current.ghost) { st.current.ghost.remove(); st.current.ghost = null } + stopAutoScroll() + setDragTask(null) + setOverCol(null) + }, []) + + useEffect(() => () => cleanup(), [cleanup]) + + /** 拖动刚结束 (350ms 内): 抑制松手合成的 click 误开详情页 */ + const justEnded = useCallback(() => Date.now() - lastEnd.current < 350, []) + + const hitColumn = (x, y) => { + const el = document.elementFromPoint(x, y) + const colEl = el?.closest?.('[data-column-id]') + return colEl ? Number(colEl.dataset.columnId) : null + } + + // 边缘自动滚动 (±36px 触发, 每帧 10px) + const autoScroll = (container, clientX) => { + if (!container) { stopAutoScroll(); return } + const r = container.getBoundingClientRect() + const edge = 36 + let dir = 0 + if (clientX < r.left + edge) dir = -1 + else if (clientX > r.right - edge) dir = 1 + st.current.autoScroll = dir + if (!dir) { stopAutoScroll(); return } + if (st.current.raf) return + const step = () => { + if (!st.current.autoScroll) return + container.scrollLeft += st.current.autoScroll * 10 + st.current.raf = requestAnimationFrame(step) + } + st.current.raf = requestAnimationFrame(step) + } + + const onPointerMove = (ev) => { + const s = st.current + if (!s.active) { + if (s.timer && (Math.abs(ev.clientX - s.startX) > 10 || Math.abs(ev.clientY - s.startY) > 10)) { + clearTimeout(s.timer); s.timer = null + } + return + } + ev.preventDefault() + moveGhost(ev.clientX, ev.clientY) + const col = hitColumn(ev.clientX, ev.clientY) + setOverCol(col) + autoScroll(document.querySelector(scrollSelector), ev.clientX) + } + + const onPointerUp = (ev) => { + const s = st.current + if (s.active) { + lastEnd.current = Date.now() + const col = hitColumn(ev.clientX, ev.clientY) + if (col && s.task && col !== s.task.columnId) onDropRef.current?.(s.task, col) + } + cleanup() + } + + // 指针监听装配 (capture + 非 passive touchmove; instant=true 把手路径 + // 再挂 touchstart preventDefault, 浏览器完全不接管该触摸) + const armListeners = (ev, instant) => { + try { ev.currentTarget.setPointerCapture(ev.pointerId) } catch { /* ignore */ } + const el = ev.currentTarget + const move = (e2) => onPointerMove(e2) + const tm = (e2) => { if (st.current.active) e2.preventDefault() } + const ts = (e2) => e2.preventDefault() + const up = (e2) => { onPointerUp(e2); detach() } + const cancel = () => { + if (st.current.active) lastEnd.current = Date.now() + cleanup() + detach() + } + const detach = () => { + el.removeEventListener('pointermove', move) + el.removeEventListener('pointerup', up) + el.removeEventListener('pointercancel', cancel) + el.removeEventListener('touchmove', tm) + el.removeEventListener('touchstart', ts) + } + el.addEventListener('pointermove', move) + el.addEventListener('pointerup', up) + el.addEventListener('pointercancel', cancel) + el.addEventListener('touchmove', tm, { passive: false }) + if (instant) el.addEventListener('touchstart', ts, { passive: false }) + } + + const startDrag = (ev, task, instant) => { + st.current = { + ...st.current, startX: ev.clientX, startY: ev.clientY, task, active: false, + srcEl: ev.currentTarget.closest('.bcard'), + } + const activate = () => { + st.current.active = true + setDragTask(task) + if (navigator.vibrate) navigator.vibrate(15) + mountGhost(st.current.startX, st.current.startY) + } + if (instant) activate() + else st.current.timer = setTimeout(activate, 400) + armListeners(ev, instant) + } + + /** 绑定到卡片: 需要卡片元素设置 touch-action: pan-y */ + const bind = useCallback((task) => ({ + onPointerDown: (ev) => { + if (ev.button !== undefined && ev.button !== 0) return + startDrag(ev, task, false) + }, + // 长按不弹系统菜单 (配合 CSS user-select:none, 防长按变文字选择) + onContextMenu: (ev) => ev.preventDefault(), + // eslint-disable-next-line react-hooks/exhaustive-deps + }), []) + + /** 绑定到卡片左侧拖动把手: 把手需 touch-action: none, 按下立即拖动 */ + const bindHandle = useCallback((task) => ({ + onPointerDown: (ev) => { + if (ev.button !== undefined && ev.button !== 0) return + ev.stopPropagation() // 不触发卡片自身的长按路径 + startDrag(ev, task, true) + }, + onContextMenu: (ev) => ev.preventDefault(), + // eslint-disable-next-line react-hooks/exhaustive-deps + }), []) + + return { dragTask, overCol, bind, bindHandle, cancel: cleanup, justEnded } +} diff --git a/src/components/task/useRowDrag.js b/src/components/task/useRowDrag.js new file mode 100644 index 0000000..c47943c --- /dev/null +++ b/src/components/task/useRowDrag.js @@ -0,0 +1,195 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react' + +// 列表视图任务行拖动: 组间换列 + 组内排序 +// 触发: 行长按 400ms, 或按住行首 ⠿ 把手 (touch-action:none + touchstart +// preventDefault, 浏览器不接管该触摸, 是可靠入口) +// 拖动: 克隆原行作为跟手浮层 (淡化), 原位保留占位; +// 手指划到其他分组 ([data-column-id]) → 换列; 划到本组某行 +// ([data-task-id]) 的上/下半 → 组内插入到其前/后; 松手提交。 +// touch-action: pan-y + 激活后非 passive touchmove preventDefault 尽力 +// 拦截滚动接管; pointercancel 只放弃不提交。 +// 注: 列表只展示有任务的列, 空列拖不进去 (用看板视图可拖空列)。 + +export default function useRowDrag({ columns, onDrop, onReorder }) { + // { task, fromIdx, toIdx }: 拖动中的行 + 当前目标列下标 + const [drag, setDrag] = useState(null) + // { id, after }: 组内落点 (插入到某行前/后) + const [over, setOver] = useState(null) + const st = useRef({ timer: null, active: false, startX: 0, startY: 0, task: null, fromIdx: 0, toIdx: 0, over: null, srcEl: null, ghost: null }) + const lastEnd = useRef(0) // 拖动结束时刻 + + // onDrop/onReorder 经 ref 桥接实时闭包 (bind 是 useCallback, 防陈旧) + const onDropRef = useRef(onDrop) + onDropRef.current = onDrop + const onReorderRef = useRef(onReorder) + onReorderRef.current = onReorder + + // 目标列顺序 = 列表分组顺序 (sortOrder) + const ordered = useMemo( + () => [...(columns || [])].sort((a, b) => a.sortOrder - b.sortOrder), + [columns], + ) + const orderedRef = useRef(ordered) + orderedRef.current = ordered + + // 跟手浮层: 激活时克隆原行挂到 body, 移动时直接改 transform + const moveGhost = (x, y) => { + const g = st.current.ghost + if (g) g.style.transform = `translate(${x}px, ${y}px) translate(-50%, -60%)` + } + const mountGhost = (x, y) => { + const src = st.current.srcEl + if (!src) return + const clone = src.cloneNode(true) + clone.classList.remove('dragging', 'dragover', 'after', 'before') + clone.style.width = `${src.offsetWidth}px` + const g = document.createElement('div') + g.className = 'drag-ghost' + g.appendChild(clone) + document.body.appendChild(g) + st.current.ghost = g + moveGhost(x, y) + } + + const cleanup = useCallback(() => { + if (st.current.timer) { clearTimeout(st.current.timer); st.current.timer = null } + st.current.active = false + st.current.over = null + st.current.srcEl = null + if (st.current.ghost) { st.current.ghost.remove(); st.current.ghost = null } + setDrag(null) + setOver(null) + }, []) + + useEffect(() => () => cleanup(), [cleanup]) + + /** 拖动刚结束 (350ms 内): 抑制松手合成的 click 误开详情页 */ + const justEnded = useCallback(() => Date.now() - lastEnd.current < 350, []) + + const onMove = (ev) => { + const s = st.current + if (!s.active) { + if (s.timer && (Math.abs(ev.clientX - s.startX) > 10 || Math.abs(ev.clientY - s.startY) > 10)) { + clearTimeout(s.timer); s.timer = null + } + return + } + ev.preventDefault() + moveGhost(ev.clientX, ev.clientY) + + // 组内落点: 命中本组 ([data-column-id]=当前列) 的某条顶层行 + const hitEl = document.elementFromPoint(ev.clientX, ev.clientY) + const rowEl = hitEl?.closest?.('[data-task-id]') + const colEl = hitEl?.closest?.('[data-column-id]') + const colId = colEl ? Number(colEl.dataset.columnId) : null + const curCol = orderedRef.current[s.toIdx]?.id + + if (rowEl && colId === s.task.columnId) { + const id = Number(rowEl.dataset.taskId) + if (id !== s.task.id) { + const r = rowEl.getBoundingClientRect() + const hit = { id, after: ev.clientY > r.top + r.height / 2 } + if (!s.over || s.over.id !== hit.id || s.over.after !== hit.after) { + s.over = hit + setOver(hit) + } + return + } + } + if (s.over) { s.over = null; setOver(null) } + + // 换列: 手指划到哪个分组就是目标列, 划出所有分组时保持上一目标 + if (colId != null) { + const to = orderedRef.current.findIndex((c) => c.id === colId) + if (to >= 0 && to !== s.toIdx) { + s.toIdx = to + setDrag({ task: s.task, fromIdx: s.fromIdx, toIdx: to }) + } + } + } + + const onUp = () => { + const s = st.current + if (s.active && s.task) { + lastEnd.current = Date.now() + if (s.over) { + onReorderRef.current?.(s.task, s.over.id, s.over.after) + } else { + const target = orderedRef.current[s.toIdx] + if (target && target.id !== s.task.columnId) onDropRef.current?.(s.task, target.id) + } + } + cleanup() + } + + // 指针监听装配 (capture + 非 passive touchmove: 激活后尽力拦截滚动接管; + // instant=true 把手路径再挂 touchstart preventDefault, 规范上禁止浏览器对 + // 整个触摸的默认处理, 彻底杜绝滚动抢占) + const armListeners = (ev, instant = false) => { + try { ev.currentTarget.setPointerCapture(ev.pointerId) } catch { /* ignore */ } + const el = ev.currentTarget + const move = (e2) => onMove(e2) + const tm = (e2) => { if (st.current.active) e2.preventDefault() } + const ts = (e2) => e2.preventDefault() + const up = () => { onUp(); detach() } + const cancel = () => { + if (st.current.active) lastEnd.current = Date.now() + cleanup() + detach() + } + const detach = () => { + el.removeEventListener('pointermove', move) + el.removeEventListener('pointerup', up) + el.removeEventListener('pointercancel', cancel) + el.removeEventListener('touchmove', tm) + el.removeEventListener('touchstart', ts) + } + el.addEventListener('pointermove', move) + el.addEventListener('pointerup', up) + el.addEventListener('pointercancel', cancel) + el.addEventListener('touchmove', tm, { passive: false }) + if (instant) el.addEventListener('touchstart', ts, { passive: false }) + } + + const startDrag = (ev, task, instant) => { + const fromIdx = orderedRef.current.findIndex((c) => c.id === task.columnId) + if (fromIdx < 0) return // 不在任何列 (如「其他」组) 的任务不支持拖动 + st.current = { + ...st.current, startX: ev.clientX, startY: ev.clientY, task, fromIdx, toIdx: fromIdx, + active: false, over: null, srcEl: ev.currentTarget.closest('.task-row'), + } + const activate = () => { + st.current.active = true + if (navigator.vibrate) navigator.vibrate(15) + setDrag({ task, fromIdx, toIdx: fromIdx }) + mountGhost(st.current.startX, st.current.startY) + } + if (instant) activate() + else st.current.timer = setTimeout(activate, 400) + armListeners(ev, instant) + } + + /** 绑定到列表行: 行元素需设置 touch-action: pan-y (长按路径, 可滚场景不可靠) */ + const bind = useCallback((task) => ({ + onPointerDown: (ev) => { + if (ev.button !== undefined && ev.button !== 0) return + startDrag(ev, task, false) + }, + // 长按不弹系统菜单 (配合 CSS user-select:none, 防长按变文字选择) + onContextMenu: (ev) => ev.preventDefault(), + // eslint-disable-next-line react-hooks/exhaustive-deps + }), []) + + /** 绑定到行首拖动把手: 把手需 touch-action: none, 按下立即拖动 */ + const bindHandle = useCallback((task) => ({ + onPointerDown: (ev) => { + if (ev.button !== undefined && ev.button !== 0) return + ev.stopPropagation() // 不触发行自身的长按路径 + startDrag(ev, task, true) + }, + onContextMenu: (ev) => ev.preventDefault(), + // eslint-disable-next-line react-hooks/exhaustive-deps + }), []) + + return { drag, over, bind, bindHandle, cancel: cleanup, justEnded } +} diff --git a/src/hooks/useKeyboardInset.js b/src/hooks/useKeyboardInset.js new file mode 100644 index 0000000..096bc21 --- /dev/null +++ b/src/hooks/useKeyboardInset.js @@ -0,0 +1,27 @@ +import { useEffect, useState } from 'react' + +// 键盘弹出时的"插入量": window.innerHeight 与 visualViewport.height 的差 +// Android WebView 键盘弹出时 visualViewport 变小; 用它把底部弹层的内容抬到键盘上方, +// 避免输入框(如任务详情里的"添加子任务")被键盘遮挡。 +// 阈值 90px 过滤掉地址栏收起之类的轻微抖动; 无 visualViewport 时恒为 0 (iOS 原生键盘通常整体缩放, 无需处理)。 +export default function useKeyboardInset() { + const [inset, setInset] = useState(0) + + useEffect(() => { + const vv = window.visualViewport + if (!vv) return + const onChange = () => { + const diff = window.innerHeight - vv.height - vv.offsetTop + setInset(diff > 90 ? Math.round(diff) : 0) + } + vv.addEventListener('resize', onChange) + vv.addEventListener('scroll', onChange) + onChange() + return () => { + vv.removeEventListener('resize', onChange) + vv.removeEventListener('scroll', onChange) + } + }, []) + + return inset +} diff --git a/src/hooks/useOverlayBack.js b/src/hooks/useOverlayBack.js new file mode 100644 index 0000000..0f87f11 --- /dev/null +++ b/src/hooks/useOverlayBack.js @@ -0,0 +1,24 @@ +import { useEffect } from 'react' + +// 弹层 / 全屏覆盖层打开时拦截返回键: 先关弹层, 而不是离开当前路由 (bug: 返回跑到上一个 Tab) +// 原理: 打开时 pushState 一条哨兵记录 (URL 不变), 返回键先消费它; +// popstate => 视为"关闭弹层"。正常关闭 (点 X / 遮罩) 时在 cleanup 里 history.back() 撤掉哨兵。 +// 注意: close 必须把 open 置 false, 否则 cleanup 不会执行、哨兵残留。 +export default function useOverlayBack(open, close) { + useEffect(() => { + if (!open) return + let closedByBack = false + window.history.pushState({ __overlay: true }, '') + const onPop = () => { + closedByBack = true + close() + } + window.addEventListener('popstate', onPop) + return () => { + window.removeEventListener('popstate', onPop) + // 正常关闭: 撤销哨兵 (不产生可见的后退, 因为 URL 未变) + if (!closedByBack) window.history.back() + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open]) +} diff --git a/src/layouts/MobileShell.jsx b/src/layouts/MobileShell.jsx index 9b07fa1..19cc6c3 100644 --- a/src/layouts/MobileShell.jsx +++ b/src/layouts/MobileShell.jsx @@ -1,13 +1,18 @@ import { useEffect, useState } from 'react' import { Outlet, useLocation } from 'react-router-dom' -import { SafeArea } from 'antd-mobile' import BottomNav from '../components/BottomNav.jsx' +import { isOffline, subscribeOffline } from '../api/client.js' // 移动端应用外壳 -// - 顶部 SafeArea: 让出手机状态栏高度 +// - 顶部安全区: 用 --safe-top 撑开 (由 main.jsx 的 calibrateSafeTop 实测校准) // - 中间
: flex:1 可滚动主区 // - 底部 TabBar: antd-mobile TabBar 自带底部安全区 -// 解决: 状态栏遮挡/底部 TabBar 遮挡/左右贴边 +// - 左上角离线标识 (0.2.1): 离线或服务不可达时显示, 请求恢复后自动隐藏 +// +// 注意: 这里不再用 antd-mobile 的 . +// 它只是一个空 div, 高度完全依赖 env(safe-area-inset-top); 而 Android WebView +// (Tauri) 下该变量常为 0, 会导致内容顶到状态栏底下 —— V0.2 把 45px 的 NavBar +// 换成 23px 的 PageHeader 后这个问题就暴露了. 改用可控高度的 div + JS 校准. export default function MobileShell() { const location = useLocation() // pathname 变化时给页面容器加动画 class (仅播放入场动画, 不重挂载子树, @@ -19,9 +24,19 @@ export default function MobileShell() { return () => clearTimeout(t) }, [location.pathname]) + // 离线状态 (窗口左上角标识) + const [offline, setOfflineState] = useState(isOffline()) + useEffect(() => subscribeOffline(setOfflineState), []) + return (
- +