Compare commits

..

2 Commits

276 changed files with 8119 additions and 30078 deletions

View File

@@ -1,153 +0,0 @@
---
description:
globs:
alwaysApply: true
---
# Absurd SQL - Cursor Development Guide
## Project Overview
Absurd SQL is a backend implementation for sql.js that enables persistent SQLite databases in the browser by using IndexedDB as a block storage system. This guide provides rules and best practices for developing with this project in Cursor.
## Project Structure
```
absurd-sql/
├── src/ # Source code
├── dist/ # Built files
├── package.json # Dependencies and scripts
├── rollup.config.js # Build configuration
└── jest.config.js # Test configuration
```
## Development Rules
### 1. Worker Thread Requirements
- All SQL operations MUST be performed in a worker thread
- Main thread should only handle worker initialization and communication
- Never block the main thread with database operations
### 2. Code Organization
- Keep worker code in separate files (e.g., `*.worker.js`)
- Use ES modules for imports/exports
- Follow the project's existing module structure
### 3. Required Headers
When developing locally or deploying, ensure these headers are set:
```
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
```
### 4. Browser Compatibility
- Primary target: Modern browsers with SharedArrayBuffer support
- Fallback mode: Safari (with limitations)
- Always test in both modes
### 5. Database Configuration
Recommended database settings:
```sql
PRAGMA journal_mode=MEMORY;
PRAGMA page_size=8192; -- Optional, but recommended
```
### 6. Development Workflow
1. Install dependencies:
```bash
yarn add @jlongster/sql.js absurd-sql
```
2. Development commands:
- `yarn build` - Build the project
- `yarn jest` - Run tests
- `yarn serve` - Start development server
### 7. Testing Guidelines
- Write tests for both SharedArrayBuffer and fallback modes
- Use Jest for testing
- Include performance benchmarks for critical operations
### 8. Performance Considerations
- Use bulk operations when possible
- Monitor read/write performance
- Consider using transactions for multiple operations
- Avoid unnecessary database connections
### 9. Error Handling
- Implement proper error handling for:
- Worker initialization failures
- Database connection issues
- Concurrent access conflicts (in fallback mode)
- Storage quota exceeded scenarios
### 10. Security Best Practices
- Never expose database operations directly to the client
- Validate all SQL queries
- Implement proper access controls
- Handle sensitive data appropriately
### 11. Code Style
- Follow ESLint configuration
- Use async/await for asynchronous operations
- Document complex database operations
- Include comments for non-obvious optimizations
### 12. Debugging
- Use `jest-debug` for debugging tests
- Monitor IndexedDB usage in browser dev tools
- Check worker communication in console
- Use performance monitoring tools
## Common Patterns
### Worker Initialization
```javascript
// Main thread
import { initBackend } from 'absurd-sql/dist/indexeddb-main-thread';
function init() {
let worker = new Worker(new URL('./index.worker.js', import.meta.url));
initBackend(worker);
}
```
### Database Setup
```javascript
// Worker thread
import initSqlJs from '@jlongster/sql.js';
import { SQLiteFS } from 'absurd-sql';
import IndexedDBBackend from 'absurd-sql/dist/indexeddb-backend';
async function setupDatabase() {
let SQL = await initSqlJs({ locateFile: file => file });
let sqlFS = new SQLiteFS(SQL.FS, new IndexedDBBackend());
SQL.register_for_idb(sqlFS);
SQL.FS.mkdir('/sql');
SQL.FS.mount(sqlFS, {}, '/sql');
return new SQL.Database('/sql/db.sqlite', { filename: true });
}
```
## Troubleshooting
### Common Issues
1. SharedArrayBuffer not available
- Check COOP/COEP headers
- Verify browser support
- Test fallback mode
2. Worker initialization failures
- Check file paths
- Verify module imports
- Check browser console for errors
3. Performance issues
- Monitor IndexedDB usage
- Check for unnecessary operations
- Verify transaction usage
## Resources
- [Project Demo](https://priceless-keller-d097e5.netlify.app/)
- [Example Project](https://github.com/jlongster/absurd-example-project)
- [Blog Post](https://jlongster.com/future-sql-web)
- [SQL.js Documentation](https://github.com/sql-js/sql.js/)

View File

@@ -7,13 +7,13 @@ alwaysApply: true
## 1. Platform Support Matrix
| Feature | Web (PWA) | Capacitor (Mobile) | Electron (Desktop) |
|---------|-----------|-------------------|-------------------|
| QR Code Scanning | WebInlineQRScanner | @capacitor-mlkit/barcode-scanning | Not Implemented |
| Deep Linking | URL Parameters | App URL Open Events | Not Implemented |
| File System | Limited (Browser API) | Capacitor Filesystem | Electron fs |
| Camera Access | MediaDevices API | Capacitor Camera | Not Implemented |
| Platform Detection | Web APIs | Capacitor.isNativePlatform() | process.env checks |
| Feature | Web (PWA) | Capacitor (Mobile) | Electron (Desktop) | PyWebView (Desktop) |
|---------|-----------|-------------------|-------------------|-------------------|
| QR Code Scanning | WebInlineQRScanner | @capacitor-mlkit/barcode-scanning | Not Implemented | Not Implemented |
| Deep Linking | URL Parameters | App URL Open Events | Not Implemented | Not Implemented |
| File System | Limited (Browser API) | Capacitor Filesystem | Electron fs | PyWebView Python Bridge |
| Camera Access | MediaDevices API | Capacitor Camera | Not Implemented | Not Implemented |
| Platform Detection | Web APIs | Capacitor.isNativePlatform() | process.env checks | process.env checks |
## 2. Project Structure
@@ -42,6 +42,7 @@ src/
├── main.common.ts # Shared initialization
├── main.capacitor.ts # Mobile entry
├── main.electron.ts # Electron entry
├── main.pywebview.ts # PyWebView entry
└── main.web.ts # Web/PWA entry
```
@@ -51,7 +52,9 @@ root/
├── vite.config.common.mts # Shared config
├── vite.config.capacitor.mts # Mobile build
├── vite.config.electron.mts # Electron build
── vite.config.web.mts # Web/PWA build
── vite.config.pywebview.mts # PyWebView build
├── vite.config.web.mts # Web/PWA build
└── vite.config.utils.mts # Build utilities
```
## 3. Service Architecture
@@ -65,7 +68,8 @@ services/
├── platforms/ # Platform-specific services
│ ├── WebPlatformService.ts
│ ├── CapacitorPlatformService.ts
── ElectronPlatformService.ts
── ElectronPlatformService.ts
│ └── PyWebViewPlatformService.ts
└── factory/ # Service factories
└── PlatformServiceFactory.ts
```
@@ -163,7 +167,8 @@ export function createBuildConfig(mode: string) {
# Build commands from package.json
"build:web": "vite build --config vite.config.web.mts",
"build:capacitor": "vite build --config vite.config.capacitor.mts",
"build:electron": "vite build --config vite.config.electron.mts"
"build:electron": "vite build --config vite.config.electron.mts",
"build:pywebview": "vite build --config vite.config.pywebview.mts"
```
## 6. Testing Strategy

View File

@@ -1,7 +1,7 @@
---
description:
globs:
alwaysApply: false
alwaysApply: true
---
# Camera Implementation Documentation

View File

@@ -1,31 +0,0 @@
---
description:
globs:
alwaysApply: true
---
use system date command to timestamp all interactions with accurate date and time
python script files must always have a blank line
remove whitespace at the end of lines
never git add or commit for me. always preview changes and commit message to use and allow me to copy and paste
✅ Preferred Commit Message Format
Short summary in the first line (concise and high-level).
Avoid long commit bodies unless truly necessary.
✅ Valued Content in Commit Messages
Specific fixes or features.
Symptoms or problems that were fixed.
Notes about tests passing or TS/linting errors being resolved (briefly).
❌ Avoid in Commit Messages
Vague terms: “improved”, “enhanced”, “better” — especially from AI.
Minor changes: small doc tweaks, one-liners, cleanup, or lint fixes.
Redundant blurbs: repeated across files or too generic.
Multiple overlapping purposes in a single commit — prefer narrow, focused commits.
Long explanations of what can be deduced from good in-line code comments.
Guiding Principle
Let code and inline documentation speak for themselves. Use commits to highlight what isn't obvious from reading the code.

View File

@@ -1,6 +0,0 @@
---
description:
globs:
alwaysApply: true
---
All references in the codebase to Dexie apply only to migration from IndexedDb to Sqlite and will be deprecated in future versions.

View File

@@ -1,171 +0,0 @@
# TimeSafari Docker Ignore File
# Author: Matthew Raymer
# Description: Excludes unnecessary files from Docker build context
#
# Benefits:
# - Faster build times
# - Smaller build context
# - Reduced image size
# - Better security (excludes sensitive files)
# Dependencies
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Build outputs
dist
dist-*
build
*.tsbuildinfo
# Development files
.git
.gitignore
README.md
CHANGELOG.md
CONTRIBUTING.md
BUILDING.md
LICENSE
# IDE and editor files
.vscode
.idea
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Dependency directories
jspm_packages/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# Test files
test-playwright
test-playwright-results
test-results
test-scripts
# Documentation
doc
# Scripts (keep only what's needed for build)
scripts/test-*.sh
scripts/*.js
scripts/README.md
# Platform-specific files
android
ios
electron
# Docker files (avoid recursive copying)
Dockerfile*
docker-compose*
.dockerignore
# CI/CD files
.github
.gitlab-ci.yml
.travis.yml
.circleci
# Temporary files
tmp
temp
# Backup files
*.bak
*.backup
# Archive files
*.tar
*.tar.gz
*.zip
*.rar
# Certificate files
*.pem
*.key
*.crt
*.p12
# Configuration files that might contain secrets
*.secrets
secrets.json
config.local.json

View File

@@ -2,12 +2,11 @@
# iOS doesn't like spaces in the app title.
TIME_SAFARI_APP_TITLE="TimeSafari_Dev"
VITE_APP_SERVER=http://localhost:8080
VITE_APP_SERVER=http://localhost:3000
# This is the claim ID for actions in the BVC project, with the JWT ID on this environment (not production).
VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F
VITE_DEFAULT_ENDORSER_API_SERVER=http://localhost:3000
# Using shared server by default to ease setup, which works for shared test users.
VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app
VITE_DEFAULT_PARTNER_API_SERVER=http://localhost:3000
#VITE_DEFAULT_PUSH_SERVER... can't be set up with localhost domain
VITE_PASSKEYS_ENABLED=true

6
.env.example Normal file
View File

@@ -0,0 +1,6 @@
# Admin DID credentials
ADMIN_DID=did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F
ADMIN_PRIVATE_KEY=2b6472c026ec2aa2c4235c994a63868fc9212d18b58f6cbfe861b52e71330f5b
# API Configuration
ENDORSER_API_URL=https://test-api.endorser.ch/api/v2/claim

View File

@@ -9,4 +9,3 @@ VITE_DEFAULT_ENDORSER_API_SERVER=https://api.endorser.ch
VITE_DEFAULT_IMAGE_API_SERVER=https://image-api.timesafari.app
VITE_DEFAULT_PARTNER_API_SERVER=https://partner-api.endorser.ch
VITE_DEFAULT_PUSH_SERVER=https://timesafari.app

View File

@@ -9,5 +9,4 @@ VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch
VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app
VITE_DEFAULT_PARTNER_API_SERVER=https://test-partner-api.endorser.ch
VITE_DEFAULT_PUSH_SERVER=https://test.timesafari.app
VITE_PASSKEYS_ENABLED=true

View File

@@ -4,12 +4,6 @@ module.exports = {
node: true,
es2022: true,
},
ignorePatterns: [
'node_modules/',
'dist/',
'dist-electron/',
'*.d.ts'
],
extends: [
"plugin:vue/vue3-recommended",
"eslint:recommended",

7
.gitignore vendored
View File

@@ -51,9 +51,6 @@ vendor/
# Build logs
build_logs/
# PWA icon files generated by capacitor-assets
icons
android/app/src/main/assets/public
android/app/src/main/res
*.log
android/app/src/main/res/
sql-wasm.wasm

1
.npmrc
View File

@@ -1 +0,0 @@
@jsr:registry=https://npm.jsr.io

View File

@@ -9,6 +9,19 @@ For a quick dev environment setup, use [pkgx](https://pkgx.dev).
- Node.js (LTS version recommended)
- npm (comes with Node.js)
- Git
- For Android builds: Android Studio with SDK installed
- For iOS builds: macOS with Xcode and ruby gems & bundle
- `pkgx +rubygems.org sh`
- ... and you may have to fix these, especially with pkgx
```bash
gem_path=$(which gem)
shortened_path="${gem_path:h:h}"
export GEM_HOME=$shortened_path
export GEM_PATH=$shortened_path
```
- For desktop builds: Additional build tools based on your OS
## Forks
@@ -41,7 +54,6 @@ Install dependencies:
1. Run the production build:
```bash
rm -rf dist
npm run build:web
```
@@ -63,18 +75,16 @@ Install dependencies:
* Update the ClickUp tasks & CHANGELOG.md & the version in package.json, run `npm install`.
* Run a build to make sure package-lock version is updated, linting works, etc: `npm install && npm run build`
* Commit everything (since the commit hash is used the app).
* Put the commit hash in the changelog (which will help you remember to bump the version in the step later).
* Put the commit hash in the changelog (which will help you remember to bump the version later).
* Tag with the new version, [online](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases) or `git tag 1.0.2 && git push origin 1.0.2`.
* Tag with the new version, [online](https://gitea.anomalistdesign.com/trent_larson/crowd-funder-for-time-pwa/releases) or `git tag 0.3.55 && git push origin 0.3.55`.
* For test, build the app (because test server is not yet set up to build):
```bash
TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari.app VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_DEFAULT_PARTNER_API_SERVER=https://test-partner-api.endorser.ch VITE_DEFAULT_PUSH_SERVER=https://test.timesafari.app VITE_PASSKEYS_ENABLED=true npm run build:web
TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari.app VITE_BVC_MEETUPS_PROJECT_CLAIM_ID=https://endorser.ch/entity/01HWE8FWHQ1YGP7GFZYYPS272F VITE_DEFAULT_ENDORSER_API_SERVER=https://test-api.endorser.ch VITE_DEFAULT_IMAGE_API_SERVER=https://test-image-api.timesafari.app VITE_DEFAULT_PARTNER_API_SERVER=https://test-partner-api.endorser.ch VITE_PASSKEYS_ENABLED=true npm run build
```
... and transfer to the test server:
@@ -93,13 +103,13 @@ TIME_SAFARI_APP_TITLE="TimeSafari_Test" VITE_APP_SERVER=https://test.timesafari.
* `pkgx +npm sh`
* `cd crowd-funder-for-time-pwa && git checkout master && git pull && git checkout 1.0.2 && npm install && npm run build:web && cd -`
* `cd crowd-funder-for-time-pwa && git checkout master && git pull && git checkout 0.3.55 && npm install && npm run build && cd -`
(The plain `npm run build:web` uses the .env.production file.)
(The plain `npm run build` uses the .env.production file.)
* Back up the time-safari/dist folder & deploy: `mv time-safari/dist time-safari-dist-prev-2 && mv crowd-funder-for-time-pwa/dist time-safari/`
* Back up the time-safari/dist folder & deploy: `mv time-safari/dist time-safari-dist-prev.0 && mv crowd-funder-for-time-pwa/dist time-safari/`
* Record the new hash in the changelog. Edit package.json to increment version & add "-beta", `npm install`, commit, and push. Also record what version is on production.
* Record the new hash in the changelog. Edit package.json to increment version & add "-beta", `npm install`, and commit. Also record what version is on production.
## Docker Deployment
@@ -231,9 +241,7 @@ docker run -d \
1. Build the electron app in production mode:
```bash
npm run build:web
npm run build:electron
npm run electron:build-mac
npm run build:electron-prod
```
2. Package the Electron app for macOS:
@@ -316,63 +324,17 @@ npm run build:electron-prod && npm run electron:start
Prerequisites: macOS with Xcode installed
#### Automated Build Script
The recommended way to build for iOS is using the automated build script:
```bash
# Standard build and open Xcode
./scripts/build-ios.sh
# Build with specific version numbers
./scripts/build-ios.sh --version 1.0.3 --build-number 35
# Build without opening Xcode (for CI/CD)
./scripts/build-ios.sh --no-xcode
# Show all available options
./scripts/build-ios.sh --help
```
The script handles all the necessary steps including:
- Environment setup and validation
- Web asset building
- Capacitor synchronization
- iOS asset generation
- Version number updates
- Xcode project opening
#### Manual Build Process
If you need to build manually or want to understand the individual steps:
#### First-time iOS Configuration
- Generate certificates inside XCode.
- Right-click on App and under Signing & Capabilities set the Team.
#### Each Release
0. First time (or if dependencies change):
- `pkgx +rubygems.org sh`
- ... and you may have to fix these, especially with pkgx:
```bash
gem_path=$(which gem)
shortened_path="${gem_path:h:h}"
export GEM_HOME=$shortened_path
export GEM_PATH=$shortened_path
```
1. Build the web assets & update ios:
1. Build the web assets:
```bash
rm -rf dist
npm run build:web
npm run build:capacitor
```
2. Update iOS project with latest build:
```bash
npx cap sync ios
```
@@ -381,20 +343,20 @@ If you need to build manually or want to understand the individual steps:
3. Copy the assets:
```bash
# It makes no sense why capacitor-assets will not run without these but it actually changes the contents.
mkdir -p ios/App/App/Assets.xcassets/AppIcon.appiconset
echo '{"images":[]}' > ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json
mkdir -p ios/App/App/Assets.xcassets/Splash.imageset
echo '{"images":[]}' > ios/App/App/Assets.xcassets/Splash.imageset/Contents.json
npx capacitor-assets generate --ios
```
4. Bump the version to match Android & package.json:
4. Bump the version to match Android:
```
cd ios/App && xcrun agvtool new-version 35 && perl -p -i -e "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 1.0.2;/g" App.xcodeproj/project.pbxproj && cd -
cd ios/App
xcrun agvtool new-version 15
# Unfortunately this edits Info.plist directly.
#xcrun agvtool new-marketing-version 0.4.5
cat App.xcodeproj/project.pbxproj | sed "s/MARKETING_VERSION = .*;/MARKETING_VERSION = 0.4.5;/g" > temp
mv temp App.xcodeproj/project.pbxproj
cd -
```
5. Open the project in Xcode:
@@ -405,25 +367,28 @@ If you need to build manually or want to understand the individual steps:
6. Use Xcode to build and run on simulator or device.
* Select Product -> Destination with some Simulator version. Then click the run arrow.
7. Release
* Someday: Under "General" we want to rename a bunch of things to "Time Safari"
* Choose Product -> Destination -> Any iOS Device
* Under "General" renamed a bunch of things to "Time Safari"
* Choose Product -> Destination -> Build Any iOS
* Choose Product -> Archive
* This will trigger a build and take time, needing user's "login" keychain password (user's login password), repeatedly.
* This will trigger a build and take time, needing user's "login" keychain password which is just their login password, repeatedly.
* If it fails with `building for 'iOS', but linking in dylib (.../.pkgx/zlib.net/v1.3.0/lib/libz.1.3.dylib) built for 'macOS'` then run XCode outside that terminal (ie. not with `npx cap open ios`).
* Click Distribute -> App Store Connect
* In AppStoreConnect, add the build to the distribution: remove the current build with the "-" when you hover over it, then "Add Build" with the new build.
* May have to go to App Review, click Submission, then hover over the build and click "-".
* It can take 15 minutes for the build to show up in the list of builds.
* You'll probably have to "Manage" something about encryption, disallowed in France.
* Then "Save" and "Add to Review" and "Resubmit to App Review".
#### First-time iOS Configuration
- Generate certificates inside XCode.
- Right-click on App and under Signing & Capabilities set the Team.
### Android Build
Prerequisites: Android Studio with Java SDK installed
Prerequisites: Android Studio with SDK installed
1. Build the web assets:
@@ -445,7 +410,7 @@ Prerequisites: Android Studio with Java SDK installed
npx capacitor-assets generate --android
```
4. Bump version to match iOS & package.json: android/app/build.gradle
4. Bump version to match iOS: android/app/build.gradle
5. Open the project in Android Studio:
@@ -462,6 +427,7 @@ Prerequisites: Android Studio with Java SDK installed
./gradlew clean
./gradlew build -Dlint.baselines.continue=true
cd -
npx cap run android
```
... or, to create the `aab` file, `bundle` instead of `build`:
@@ -477,9 +443,7 @@ Prerequisites: Android Studio with Java SDK installed
* Then `bundleRelease`:
```bash
cd android
./gradlew bundleRelease -Dlint.baselines.continue=true
cd -
```
... and find your `aab` file at app/build/outputs/bundle/release
@@ -492,10 +456,8 @@ At play.google.com/console:
- Hit "Next".
- Save, go to "Publishing Overview" as prompted, and click "Send changes for review".
- Note that if you add testers, you have to go to "Publishing Overview" and send those changes or your (closed) testers won't see it.
## Android Configuration for deep links
## First-time Android Configuration for deep links
You must add the following intent filter to the `android/app/src/main/AndroidManifest.xml` file:
@@ -506,6 +468,4 @@ You must add the following intent filter to the `android/app/src/main/AndroidMan
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="timesafari" />
</intent-filter>
```
... though when we tried that most recently it failed to 'build' the APK with: http(s) scheme and host attribute are missing, but are required for Android App Links [AppLinkUrlError]
```

View File

@@ -6,35 +6,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.0.2] - 2025.06.20 - 276e0a741bc327de3380c4e508cccb7fee58c06d
### Added
- Version on feed title
## [1.0.1] - 2025.06.20
### Added
- Allow a user to block someone else's content from view
## [1.0.0] - 2025.06.20 - 5aa693de6337e5dbb278bfddc6bd39094bc14f73
### Added
- Web-oriented migration from IndexedDB to SQLite
## [0.5.8]
### Added
- /deep-link/ path for URLs that are shared with people
### Changed
- External links now go to /deep-link/...
- Feed visuals now have arrow imagery from giver to receiver
## [0.4.7]
### Fixed
- Cameras everywhere
### Changed
- IndexedDB -> SQLite
## [0.4.5] - 2025.02.23
### Added

View File

@@ -1,209 +1,36 @@
# TimeSafari Docker Build
# Author: Matthew Raymer
# Description: Multi-stage Docker build for TimeSafari web application
#
# Build Process:
# 1. Base stage: Node.js with build dependencies
# 2. Builder stage: Compile web assets with Vite
# 3. Production stage: Nginx server with optimized assets
#
# Security Features:
# - Non-root user execution
# - Minimal attack surface with Alpine Linux
# - Multi-stage build to reduce image size
# - No build dependencies in final image
#
# Usage:
# Production: docker build -t timesafari:latest .
# Staging: docker build --build-arg BUILD_MODE=staging -t timesafari:staging .
# Development: docker build --build-arg BUILD_MODE=development -t timesafari:dev .
#
# Build Arguments:
# BUILD_MODE: development, staging, or production (default: production)
# NODE_ENV: node environment (default: production)
# VITE_PLATFORM: vite platform (default: web)
# VITE_PWA_ENABLED: enable PWA (default: true)
# VITE_DISABLE_PWA: disable PWA (default: false)
#
# Environment Variables:
# NODE_ENV: Build environment (development/production)
# VITE_APP_SERVER: Application server URL
# VITE_DEFAULT_ENDORSER_API_SERVER: Endorser API server URL
# VITE_DEFAULT_IMAGE_API_SERVER: Image API server URL
# VITE_DEFAULT_PARTNER_API_SERVER: Partner API server URL
# VITE_DEFAULT_PUSH_SERVER: Push notification server URL
# VITE_PASSKEYS_ENABLED: Enable passkeys feature
# Build stage
FROM node:22-alpine3.20 AS builder
# =============================================================================
# BASE STAGE - Common dependencies and setup
# =============================================================================
FROM node:22-alpine3.20 AS base
# Install build dependencies
# Install system dependencies for build process
RUN apk add --no-cache \
bash \
git \
python3 \
py3-pip \
py3-setuptools \
make \
g++ \
gcc \
&& rm -rf /var/cache/apk/*
# Create non-root user for security
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001
RUN apk add --no-cache bash git python3 py3-pip py3-setuptools make g++ gcc
# Set working directory
WORKDIR /app
# Copy package files for dependency installation
# Copy package files
COPY package*.json ./
# Install dependencies with security audit
RUN npm ci --only=production --audit --fund=false && \
npm audit fix --audit-level=moderate || true
# =============================================================================
# BUILDER STAGE - Compile web assets
# =============================================================================
FROM base AS builder
# Define build arguments with defaults
ARG BUILD_MODE=production
ARG NODE_ENV=production
ARG VITE_PLATFORM=web
ARG VITE_PWA_ENABLED=true
ARG VITE_DISABLE_PWA=false
# Set environment variables from build arguments
ENV BUILD_MODE=${BUILD_MODE}
ENV NODE_ENV=${NODE_ENV}
ENV VITE_PLATFORM=${VITE_PLATFORM}
ENV VITE_PWA_ENABLED=${VITE_PWA_ENABLED}
ENV VITE_DISABLE_PWA=${VITE_DISABLE_PWA}
# Install all dependencies (including dev dependencies)
RUN npm ci --audit --fund=false && \
npm audit fix --audit-level=moderate || true
# Install dependencies
RUN npm ci
# Copy source code
COPY . .
# Build the application with proper error handling
RUN echo "Building TimeSafari in ${BUILD_MODE} mode..." && \
npm run build:web || (echo "Build failed. Check the logs above." && exit 1)
# Build the application
RUN npm run build:web
# Verify build output exists
RUN ls -la dist/ || (echo "Build output not found in dist/ directory" && exit 1)
# =============================================================================
# PRODUCTION STAGE - Nginx server
# =============================================================================
FROM nginx:alpine AS production
# Define build arguments for production stage
ARG BUILD_MODE=production
ARG NODE_ENV=production
# Set environment variables
ENV BUILD_MODE=${BUILD_MODE}
ENV NODE_ENV=${NODE_ENV}
# Install security updates and clean cache
RUN apk update && \
apk upgrade && \
apk add --no-cache \
curl \
&& rm -rf /var/cache/apk/*
# Create non-root user for nginx
RUN addgroup -g 1001 -S nginx && \
adduser -S nginx -u 1001 -G nginx
# Copy appropriate nginx configuration based on build mode
COPY docker/nginx.conf /etc/nginx/nginx.conf
COPY docker/default.conf /etc/nginx/conf.d/default.conf
# Copy staging configuration if needed
COPY docker/staging.conf /etc/nginx/conf.d/staging.conf
# Production stage
FROM nginx:alpine
# Copy built assets from builder stage
COPY --from=builder --chown=nginx:nginx /app/dist /usr/share/nginx/html
COPY --from=builder /app/dist /usr/share/nginx/html
# Create necessary directories with proper permissions
RUN mkdir -p /var/cache/nginx /var/log/nginx /var/run && \
chown -R nginx:nginx /var/cache/nginx /var/log/nginx /var/run && \
chown -R nginx:nginx /usr/share/nginx/html
# Switch to non-root user
USER nginx
# Copy nginx configuration if needed
# COPY nginx.conf /etc/nginx/conf.d/default.conf
# Expose port 80
EXPOSE 80
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost/ || exit 1
# Start nginx with proper signal handling
CMD ["nginx", "-g", "daemon off;"]
# =============================================================================
# DEVELOPMENT STAGE - For development with hot reloading
# =============================================================================
FROM base AS development
# Define build arguments for development stage
ARG BUILD_MODE=development
ARG NODE_ENV=development
ARG VITE_PLATFORM=web
ARG VITE_PWA_ENABLED=true
ARG VITE_DISABLE_PWA=false
# Set environment variables
ENV BUILD_MODE=${BUILD_MODE}
ENV NODE_ENV=${NODE_ENV}
ENV VITE_PLATFORM=${VITE_PLATFORM}
ENV VITE_PWA_ENABLED=${VITE_PWA_ENABLED}
ENV VITE_DISABLE_PWA=${VITE_DISABLE_PWA}
# Install all dependencies including dev dependencies
RUN npm ci --audit --fund=false && \
npm audit fix --audit-level=moderate || true
# Copy source code
COPY . .
# Expose development port
EXPOSE 5173
# Start development server
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]
# =============================================================================
# STAGING STAGE - For staging environment testing
# =============================================================================
FROM production AS staging
# Define build arguments for staging stage
ARG BUILD_MODE=staging
ARG NODE_ENV=staging
# Set environment variables
ENV BUILD_MODE=${BUILD_MODE}
ENV NODE_ENV=${NODE_ENV}
# Copy staging-specific nginx configuration
COPY docker/staging.conf /etc/nginx/conf.d/default.conf
# Expose port 80
EXPOSE 80
# Health check for staging
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost/health || exit 1
# Start nginx
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -3,32 +3,6 @@
[Time Safari](https://timesafari.org/) allows people to ease into collaboration: start with expressions of gratitude
and expand to crowd-fund with time & money, then record and see the impact of contributions.
## Database Migration Status
**Current Status**: The application is undergoing a migration from Dexie (IndexedDB) to SQLite using absurd-sql. This migration is in **Phase 2** with a well-defined migration fence in place.
### Migration Progress
-**SQLite Database Service**: Fully implemented with absurd-sql
-**Platform Service Layer**: Unified database interface across platforms
-**Settings Migration**: Core user settings transferred
-**Account Migration**: Identity and key management
- 🔄 **Contact Migration**: User contact data (via import interface)
- 📋 **Code Cleanup**: Remove unused Dexie imports
### Migration Fence
The migration is controlled by a **migration fence** that separates legacy Dexie code from the new SQLite implementation. See [Migration Fence Definition](doc/migration-fence-definition.md) for complete details.
**Key Points**:
- Legacy Dexie database is disabled by default (`USE_DEXIE_DB = false`)
- All database operations go through `PlatformService`
- Migration tools provide controlled access to both databases
- Clear separation between legacy and new code
### Migration Documentation
- [Migration Guide](doc/migration-to-wa-sqlite.md) - Complete migration process
- [Migration Fence Definition](doc/migration-fence-definition.md) - Fence boundaries and rules
- [Database Migration Guide](doc/database-migration-guide.md) - User-facing migration tools
## Roadmap
See [project.task.yaml](project.task.yaml) for current priorities.
@@ -47,10 +21,16 @@ npm run dev
See [BUILDING.md](BUILDING.md) for more details.
## Tests
See [TESTING.md](test-playwright/TESTING.md) for detailed test instructions.
## Icons
Application icons are in the `assets` directory, processed by the `capacitor-assets` command.
@@ -86,21 +66,6 @@ Key principles:
- Common interfaces are shared through `common.ts`
- Type definitions are generated from Zod schemas where possible
### Database Architecture
The application uses a platform-agnostic database layer:
* `src/services/PlatformService.ts` - Database interface definition
* `src/services/PlatformServiceFactory.ts` - Platform-specific service factory
* `src/services/AbsurdSqlDatabaseService.ts` - SQLite implementation
* `src/db/` - Legacy Dexie database (migration in progress)
**Development Guidelines**:
- Always use `PlatformService` for database operations
- Never import Dexie directly in application code
- Test with `USE_DEXIE_DB = false` for new features
- Use migration tools for data transfer between systems
### Kudos
Gifts make the world go 'round!

View File

@@ -31,8 +31,8 @@ android {
applicationId "app.timesafari.app"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 35
versionName "1.0.2"
versionCode 18
versionName "0.4.7"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
@@ -91,8 +91,6 @@ dependencies {
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
implementation project(':capacitor-android')
implementation project(':capacitor-community-sqlite')
implementation "androidx.biometric:biometric:1.2.0-alpha05"
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"

View File

@@ -9,7 +9,6 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-community-sqlite')
implementation project(':capacitor-mlkit-barcode-scanning')
implementation project(':capacitor-app')
implementation project(':capacitor-camera')

View File

@@ -2,6 +2,7 @@
"appId": "app.timesafari",
"appName": "TimeSafari",
"webDir": "dist",
"bundledWebRuntime": false,
"server": {
"cleartext": true
},
@@ -15,93 +16,6 @@
}
]
}
},
"CapacitorSQLite": {
"iosDatabaseLocation": "Library/CapacitorDatabase",
"iosIsEncryption": false,
"iosBiometric": {
"biometricAuth": false,
"biometricTitle": "Biometric login for TimeSafari"
},
"androidIsEncryption": false,
"androidBiometric": {
"biometricAuth": false,
"biometricTitle": "Biometric login for TimeSafari"
},
"electronIsEncryption": false
}
},
"ios": {
"contentInset": "never",
"allowsLinkPreview": true,
"scrollEnabled": true,
"limitsNavigationsToAppBoundDomains": true,
"backgroundColor": "#ffffff",
"allowNavigation": [
"*.timesafari.app",
"*.jsdelivr.net",
"api.endorser.ch"
]
},
"android": {
"allowMixedContent": false,
"captureInput": true,
"webContentsDebuggingEnabled": false,
"allowNavigation": [
"*.timesafari.app",
"*.jsdelivr.net",
"api.endorser.ch"
]
},
"electron": {
"deepLinking": {
"schemes": [
"timesafari"
]
},
"buildOptions": {
"appId": "app.timesafari",
"productName": "TimeSafari",
"directories": {
"output": "dist-electron-packages"
},
"files": [
"dist/**/*",
"electron/**/*"
],
"mac": {
"category": "public.app-category.productivity",
"target": [
{
"target": "dmg",
"arch": [
"x64",
"arm64"
]
}
]
},
"win": {
"target": [
{
"target": "nsis",
"arch": [
"x64"
]
}
]
},
"linux": {
"target": [
{
"target": "AppImage",
"arch": [
"x64"
]
}
],
"category": "Utility"
}
}
}
}

View File

@@ -1,8 +1,4 @@
[
{
"pkg": "@capacitor-community/sqlite",
"classpath": "com.getcapacitor.community.database.sqlite.CapacitorSQLitePlugin"
},
{
"pkg": "@capacitor-mlkit/barcode-scanning",
"classpath": "io.capawesome.capacitorjs.plugins.mlkit.barcodescanning.BarcodeScannerPlugin"

View File

@@ -1,15 +1,7 @@
package app.timesafari;
import android.os.Bundle;
import com.getcapacitor.BridgeActivity;
//import com.getcapacitor.community.sqlite.SQLite;
public class MainActivity extends BridgeActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Initialize SQLite
//registerPlugin(SQLite.class);
}
// ... existing code ...
}

View File

@@ -0,0 +1,5 @@
package timesafari.app;
import com.getcapacitor.BridgeActivity;
public class MainActivity extends BridgeActivity {}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -1,9 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background>
<inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
</background>
<foreground>
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
</foreground>
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@@ -1,9 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background>
<inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
</background>
<foreground>
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
</foreground>
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -7,7 +7,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.10.1'
classpath 'com.android.tools.build:gradle:8.9.1'
classpath 'com.google.gms:google-services:4.4.0'
// NOTE: Do not place your application dependencies here; they belong

View File

@@ -2,9 +2,6 @@
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor')
include ':capacitor-community-sqlite'
project(':capacitor-community-sqlite').projectDir = new File('../node_modules/@capacitor-community/sqlite/android')
include ':capacitor-mlkit-barcode-scanning'
project(':capacitor-mlkit-barcode-scanning').projectDir = new File('../node_modules/@capacitor-mlkit/barcode-scanning/android')

BIN
assets/icon-only.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

View File

@@ -2,6 +2,7 @@
"appId": "app.timesafari",
"appName": "TimeSafari",
"webDir": "dist",
"bundledWebRuntime": false,
"server": {
"cleartext": true
},
@@ -15,84 +16,6 @@
}
]
}
},
"CapacitorSQLite": {
"iosDatabaseLocation": "Library/CapacitorDatabase",
"iosIsEncryption": false,
"iosBiometric": {
"biometricAuth": false,
"biometricTitle": "Biometric login for TimeSafari"
},
"androidIsEncryption": false,
"androidBiometric": {
"biometricAuth": false,
"biometricTitle": "Biometric login for TimeSafari"
},
"electronIsEncryption": false
}
},
"ios": {
"contentInset": "never",
"allowsLinkPreview": true,
"scrollEnabled": true,
"limitsNavigationsToAppBoundDomains": true,
"backgroundColor": "#ffffff",
"allowNavigation": [
"*.timesafari.app",
"*.jsdelivr.net",
"api.endorser.ch"
]
},
"android": {
"allowMixedContent": false,
"captureInput": true,
"webContentsDebuggingEnabled": false,
"allowNavigation": [
"*.timesafari.app",
"*.jsdelivr.net",
"api.endorser.ch"
]
},
"electron": {
"deepLinking": {
"schemes": ["timesafari"]
},
"buildOptions": {
"appId": "app.timesafari",
"productName": "TimeSafari",
"directories": {
"output": "dist-electron-packages"
},
"files": [
"dist/**/*",
"electron/**/*"
],
"mac": {
"category": "public.app-category.productivity",
"target": [
{
"target": "dmg",
"arch": ["x64", "arm64"]
}
]
},
"win": {
"target": [
{
"target": "nsis",
"arch": ["x64"]
}
]
},
"linux": {
"target": [
{
"target": "AppImage",
"arch": ["x64"]
}
],
"category": "Utility"
}
}
}
}

View File

@@ -100,7 +100,6 @@ try {
- `src/interfaces/deepLinks.ts`: Type definitions and validation schemas
- `src/services/deepLinks.ts`: Deep link processing service
- `src/main.capacitor.ts`: Capacitor integration
- `src/views/DeepLinkRedirectView.vue`: Page to handle links to both mobile and web
## Type Safety Examples

View File

@@ -1,51 +0,0 @@
# TimeSafari Build Modernization Context
**Author:** Matthew Raymer
## Motivation
- Eliminate manual hacks and post-build scripts for Electron builds
- Ensure maintainability, reproducibility, and security of build outputs
- Unify build, test, and deployment scripts for developer experience and CI/CD
## Key Technical Decisions
- **Vite is the single source of truth for build output**
- All Electron build output (main process, preload, renderer HTML/CSS/JS) is managed by `vite.config.electron.mts`
- **CSS injection for Electron is handled by a Vite plugin**
- No more manual HTML/CSS edits or post-build scripts
- **Build scripts are unified and robust**
- Use `./scripts/build-electron.sh` for all Electron builds
- No more `fix-inject-css.js` or similar hacks
- **Output structure is deterministic and ASAR-friendly**
- Main process: `dist-electron/main.js`
- Preload: `dist-electron/preload.js`
- Renderer assets: `dist-electron/www/` (HTML, CSS, JS)
## Security & Maintenance Checklist
- [x] All scripts and configs are committed and documented
- [x] No manual file hacks remain
- [x] All build output is deterministic and reproducible
- [x] No sensitive data is exposed in the build process
- [x] Documentation (`BUILDING.md`) is up to date
## How to Build Electron
1. Run:
```bash
./scripts/build-electron.sh
```
2. Output will be in `dist-electron/`:
- `main.js`, `preload.js` in root
- `www/` contains all renderer assets
3. No manual post-processing is required
## Customization
- **Vite config:** All build output and asset handling is controlled in `vite.config.electron.mts`
- **CSS/HTML injection:** Use Vite plugins (see `electron-css-injection` in the config) for further customization
- **Build scripts:** All orchestration is in `scripts/` and documented in `BUILDING.md`
## For Future Developers
- Always use Vite plugins/config for build output changes
- Never manually edit built files or inject assets post-build
- Keep documentation and scripts in sync with the build process
---
This file documents the context and rationale for the build modernization and should be included in the repository for onboarding and future reference.

View File

@@ -1,295 +0,0 @@
# Database Migration Guide
## Overview
The Database Migration feature allows you to compare and migrate data between Dexie (IndexedDB) and SQLite databases in the TimeSafari application. This is particularly useful during the transition from the old Dexie-based storage system to the new SQLite-based system.
## Features
### 1. Database Comparison
- Compare data between Dexie and SQLite databases
- View detailed differences in contacts and settings
- Identify added, modified, and missing records
- Export comparison results for analysis
### 2. Data Migration
- Migrate contacts from Dexie to SQLite
- Migrate settings from Dexie to SQLite
- Option to overwrite existing records or skip them
- Comprehensive error handling and reporting
### 3. User Interface
- Modern, responsive UI built with Tailwind CSS
- Real-time loading states and progress indicators
- Clear success and error messaging
- Export functionality for comparison data
## Prerequisites
### Enable Dexie Database
Before using the migration features, you must enable the Dexie database by setting:
```typescript
// In constants/app.ts
export const USE_DEXIE_DB = true;
```
**Note**: This should only be enabled temporarily during migration. Remember to set it back to `false` after migration is complete.
## Accessing the Migration Interface
1. Navigate to the **Account** page in the TimeSafari app
2. Scroll down to find the **Database Migration** link
3. Click the link to open the migration interface
## Using the Migration Interface
### Step 1: Compare Databases
1. Click the **"Compare Databases"** button
2. The system will retrieve data from both Dexie and SQLite databases
3. Review the comparison results showing:
- Summary counts for each database
- Detailed differences (added, modified, missing records)
- Specific records that need attention
### Step 2: Review Differences
The comparison results are displayed in several sections:
#### Summary Cards
- **Dexie Contacts**: Number of contacts in Dexie database
- **SQLite Contacts**: Number of contacts in SQLite database
- **Dexie Settings**: Number of settings in Dexie database
- **SQLite Settings**: Number of settings in SQLite database
#### Contact Differences
- **Added**: Contacts in Dexie but not in SQLite
- **Modified**: Contacts that differ between databases
- **Missing**: Contacts in SQLite but not in Dexie
#### Settings Differences
- **Added**: Settings in Dexie but not in SQLite
- **Modified**: Settings that differ between databases
- **Missing**: Settings in SQLite but not in Dexie
### Step 3: Configure Migration Options
Before migrating data, configure the migration options:
- **Overwrite existing records**: When enabled, existing records in SQLite will be updated with data from Dexie. When disabled, existing records will be skipped.
### Step 4: Migrate Data
#### Migrate Contacts
1. Click the **"Migrate Contacts"** button
2. The system will transfer contacts from Dexie to SQLite
3. Review the migration results showing:
- Number of contacts successfully migrated
- Any warnings or errors encountered
#### Migrate Settings
1. Click the **"Migrate Settings"** button
2. The system will transfer settings from Dexie to SQLite
3. Review the migration results showing:
- Number of settings successfully migrated
- Any warnings or errors encountered
### Step 5: Export Comparison (Optional)
1. Click the **"Export Comparison"** button
2. A JSON file will be downloaded containing the complete comparison data
3. This file can be used for analysis or backup purposes
## Migration Process Details
### Contact Migration
The contact migration process:
1. **Retrieves** all contacts from Dexie database
2. **Checks** for existing contacts in SQLite by DID
3. **Inserts** new contacts or **updates** existing ones (if overwrite is enabled)
4. **Handles** complex fields like `contactMethods` (JSON arrays)
5. **Reports** success/failure for each contact
### Settings Migration
The settings migration process:
1. **Retrieves** all settings from Dexie database
2. **Focuses** on key user-facing settings:
- `firstName`
- `isRegistered`
- `profileImageUrl`
- `showShortcutBvc`
- `searchBoxes`
3. **Preserves** other settings in SQLite
4. **Reports** success/failure for each setting
## Error Handling
### Common Issues
#### Dexie Database Not Enabled
**Error**: "Dexie database is not enabled"
**Solution**: Set `USE_DEXIE_DB = true` in `constants/app.ts`
#### Database Connection Issues
**Error**: "Failed to retrieve Dexie contacts"
**Solution**: Check that the Dexie database is properly initialized and accessible
#### SQLite Query Errors
**Error**: "Failed to retrieve SQLite contacts"
**Solution**: Verify that the SQLite database is properly set up and the platform service is working
#### Migration Failures
**Error**: "Migration failed: [specific error]"
**Solution**: Review the error details and check data integrity in both databases
### Error Recovery
1. **Review** the error messages carefully
2. **Check** the browser console for additional details
3. **Verify** database connectivity and permissions
4. **Retry** the operation if appropriate
5. **Export** comparison data for manual review if needed
## Best Practices
### Before Migration
1. **Backup** your data if possible
2. **Test** the migration on a small dataset first
3. **Verify** that both databases are accessible
4. **Review** the comparison results before migrating
### During Migration
1. **Don't** interrupt the migration process
2. **Monitor** the progress and error messages
3. **Note** any warnings or skipped records
4. **Export** comparison data for reference
### After Migration
1. **Verify** that data was migrated correctly
2. **Test** the application functionality
3. **Disable** Dexie database (`USE_DEXIE_DB = false`)
4. **Clean up** any temporary files or exports
## Technical Details
### Database Schema
The migration handles the following data structures:
#### Contacts Table
```typescript
interface Contact {
did: string; // Decentralized Identifier
name: string; // Contact name
contactMethods: ContactMethod[]; // Array of contact methods
nextPubKeyHashB64: string; // Next public key hash
notes: string; // Contact notes
profileImageUrl: string; // Profile image URL
publicKeyBase64: string; // Public key in base64
seesMe: boolean; // Visibility flag
registered: boolean; // Registration status
}
```
#### Settings Table
```typescript
interface Settings {
id: number; // Settings ID
accountDid: string; // Account DID
activeDid: string; // Active DID
firstName: string; // User's first name
isRegistered: boolean; // Registration status
profileImageUrl: string; // Profile image URL
showShortcutBvc: boolean; // UI preference
searchBoxes: any[]; // Search configuration
// ... other fields
}
```
### Migration Logic
The migration service uses sophisticated comparison logic:
1. **Primary Key Matching**: Uses DID for contacts, ID for settings
2. **Deep Comparison**: Compares all fields including complex objects
3. **JSON Handling**: Properly handles JSON fields like `contactMethods` and `searchBoxes`
4. **Conflict Resolution**: Provides options for handling existing records
### Performance Considerations
- **Batch Processing**: Processes records one by one for reliability
- **Error Isolation**: Individual record failures don't stop the entire migration
- **Memory Management**: Handles large datasets efficiently
- **Progress Reporting**: Provides real-time feedback during migration
## Troubleshooting
### Migration Stuck
If the migration appears to be stuck:
1. **Check** the browser console for errors
2. **Refresh** the page and try again
3. **Verify** database connectivity
4. **Check** for large datasets that might take time
### Incomplete Migration
If migration doesn't complete:
1. **Review** error messages
2. **Check** data integrity in both databases
3. **Export** comparison data for manual review
4. **Consider** migrating in smaller batches
### Data Inconsistencies
If you notice data inconsistencies:
1. **Export** comparison data
2. **Review** the differences carefully
3. **Manually** verify critical records
4. **Consider** selective migration of specific records
## Support
For issues with the Database Migration feature:
1. **Check** this documentation first
2. **Review** the browser console for error details
3. **Export** comparison data for analysis
4. **Contact** the development team with specific error details
## Security Considerations
- **Data Privacy**: Migration data is processed locally and not sent to external servers
- **Access Control**: Only users with access to the account can perform migration
- **Data Integrity**: Migration preserves data integrity and handles conflicts gracefully
- **Audit Trail**: Export functionality provides an audit trail of migration operations
---
**Note**: This migration tool is designed for the transition period between database systems. Once migration is complete and verified, the Dexie database should be disabled to avoid confusion and potential data conflicts.

View File

@@ -1,418 +0,0 @@
# Dexie to absurd-sql Mapping Guide
## Schema Mapping
### Current Dexie Schema
```typescript
// Current Dexie schema
const db = new Dexie('TimeSafariDB');
db.version(1).stores({
accounts: 'did, publicKeyHex, createdAt, updatedAt',
settings: 'key, value, updatedAt',
contacts: 'id, did, name, createdAt, updatedAt'
});
```
### New SQLite Schema
```sql
-- New SQLite schema
CREATE TABLE accounts (
did TEXT PRIMARY KEY,
public_key_hex TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE contacts (
id TEXT PRIMARY KEY,
did TEXT NOT NULL,
name TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY (did) REFERENCES accounts(did)
);
-- Indexes for performance
CREATE INDEX idx_accounts_created_at ON accounts(created_at);
CREATE INDEX idx_contacts_did ON contacts(did);
CREATE INDEX idx_settings_updated_at ON settings(updated_at);
```
## Query Mapping
### 1. Account Operations
#### Get Account by DID
```typescript
// Dexie
const account = await db.accounts.get(did);
// absurd-sql
const result = await db.exec(`
SELECT * FROM accounts WHERE did = ?
`, [did]);
const account = result[0]?.values[0];
```
#### Get All Accounts
```typescript
// Dexie
const accounts = await db.accounts.toArray();
// absurd-sql
const result = await db.exec(`
SELECT * FROM accounts ORDER BY created_at DESC
`);
const accounts = result[0]?.values || [];
```
#### Add Account
```typescript
// Dexie
await db.accounts.add({
did,
publicKeyHex,
createdAt: Date.now(),
updatedAt: Date.now()
});
// absurd-sql
await db.run(`
INSERT INTO accounts (did, public_key_hex, created_at, updated_at)
VALUES (?, ?, ?, ?)
`, [did, publicKeyHex, Date.now(), Date.now()]);
```
#### Update Account
```typescript
// Dexie
await db.accounts.update(did, {
publicKeyHex,
updatedAt: Date.now()
});
// absurd-sql
await db.run(`
UPDATE accounts
SET public_key_hex = ?, updated_at = ?
WHERE did = ?
`, [publicKeyHex, Date.now(), did]);
```
### 2. Settings Operations
#### Get Setting
```typescript
// Dexie
const setting = await db.settings.get(key);
// absurd-sql
const result = await db.exec(`
SELECT * FROM settings WHERE key = ?
`, [key]);
const setting = result[0]?.values[0];
```
#### Set Setting
```typescript
// Dexie
await db.settings.put({
key,
value,
updatedAt: Date.now()
});
// absurd-sql
await db.run(`
INSERT INTO settings (key, value, updated_at)
VALUES (?, ?, ?)
ON CONFLICT(key) DO UPDATE SET
value = excluded.value,
updated_at = excluded.updated_at
`, [key, value, Date.now()]);
```
### 3. Contact Operations
#### Get Contacts by Account
```typescript
// Dexie
const contacts = await db.contacts
.where('did')
.equals(accountDid)
.toArray();
// absurd-sql
const result = await db.exec(`
SELECT * FROM contacts
WHERE did = ?
ORDER BY created_at DESC
`, [accountDid]);
const contacts = result[0]?.values || [];
```
#### Add Contact
```typescript
// Dexie
await db.contacts.add({
id: generateId(),
did: accountDid,
name,
createdAt: Date.now(),
updatedAt: Date.now()
});
// absurd-sql
await db.run(`
INSERT INTO contacts (id, did, name, created_at, updated_at)
VALUES (?, ?, ?, ?, ?)
`, [generateId(), accountDid, name, Date.now(), Date.now()]);
```
## Transaction Mapping
### Batch Operations
```typescript
// Dexie
await db.transaction('rw', [db.accounts, db.contacts], async () => {
await db.accounts.add(account);
await db.contacts.bulkAdd(contacts);
});
// absurd-sql
await db.exec('BEGIN TRANSACTION;');
try {
await db.run(`
INSERT INTO accounts (did, public_key_hex, created_at, updated_at)
VALUES (?, ?, ?, ?)
`, [account.did, account.publicKeyHex, account.createdAt, account.updatedAt]);
for (const contact of contacts) {
await db.run(`
INSERT INTO contacts (id, did, name, created_at, updated_at)
VALUES (?, ?, ?, ?, ?)
`, [contact.id, contact.did, contact.name, contact.createdAt, contact.updatedAt]);
}
await db.exec('COMMIT;');
} catch (error) {
await db.exec('ROLLBACK;');
throw error;
}
```
## Migration Helper Functions
### 1. Data Export (Dexie to JSON)
```typescript
async function exportDexieData(): Promise<MigrationData> {
const db = new Dexie('TimeSafariDB');
return {
accounts: await db.accounts.toArray(),
settings: await db.settings.toArray(),
contacts: await db.contacts.toArray(),
metadata: {
version: '1.0.0',
timestamp: Date.now(),
dexieVersion: Dexie.version
}
};
}
```
### 2. Data Import (JSON to absurd-sql)
```typescript
async function importToAbsurdSql(data: MigrationData): Promise<void> {
await db.exec('BEGIN TRANSACTION;');
try {
// Import accounts
for (const account of data.accounts) {
await db.run(`
INSERT INTO accounts (did, public_key_hex, created_at, updated_at)
VALUES (?, ?, ?, ?)
`, [account.did, account.publicKeyHex, account.createdAt, account.updatedAt]);
}
// Import settings
for (const setting of data.settings) {
await db.run(`
INSERT INTO settings (key, value, updated_at)
VALUES (?, ?, ?)
`, [setting.key, setting.value, setting.updatedAt]);
}
// Import contacts
for (const contact of data.contacts) {
await db.run(`
INSERT INTO contacts (id, did, name, created_at, updated_at)
VALUES (?, ?, ?, ?, ?)
`, [contact.id, contact.did, contact.name, contact.createdAt, contact.updatedAt]);
}
await db.exec('COMMIT;');
} catch (error) {
await db.exec('ROLLBACK;');
throw error;
}
}
```
### 3. Verification
```typescript
async function verifyMigration(dexieData: MigrationData): Promise<boolean> {
// Verify account count
const accountResult = await db.exec('SELECT COUNT(*) as count FROM accounts');
const accountCount = accountResult[0].values[0][0];
if (accountCount !== dexieData.accounts.length) {
return false;
}
// Verify settings count
const settingsResult = await db.exec('SELECT COUNT(*) as count FROM settings');
const settingsCount = settingsResult[0].values[0][0];
if (settingsCount !== dexieData.settings.length) {
return false;
}
// Verify contacts count
const contactsResult = await db.exec('SELECT COUNT(*) as count FROM contacts');
const contactsCount = contactsResult[0].values[0][0];
if (contactsCount !== dexieData.contacts.length) {
return false;
}
// Verify data integrity
for (const account of dexieData.accounts) {
const result = await db.exec(
'SELECT * FROM accounts WHERE did = ?',
[account.did]
);
const migratedAccount = result[0]?.values[0];
if (!migratedAccount ||
migratedAccount[1] !== account.publicKeyHex) { // public_key_hex is second column
return false;
}
}
return true;
}
```
## Performance Considerations
### 1. Indexing
- Dexie automatically creates indexes based on the schema
- absurd-sql requires explicit index creation
- Added indexes for frequently queried fields
- Use `PRAGMA journal_mode=MEMORY;` for better performance
### 2. Batch Operations
- Dexie has built-in bulk operations
- absurd-sql uses transactions for batch operations
- Consider chunking large datasets
- Use prepared statements for repeated queries
### 3. Query Optimization
- Dexie uses IndexedDB's native indexing
- absurd-sql requires explicit query optimization
- Use prepared statements for repeated queries
- Consider using `PRAGMA synchronous=NORMAL;` for better performance
## Error Handling
### 1. Common Errors
```typescript
// Dexie errors
try {
await db.accounts.add(account);
} catch (error) {
if (error instanceof Dexie.ConstraintError) {
// Handle duplicate key
}
}
// absurd-sql errors
try {
await db.run(`
INSERT INTO accounts (did, public_key_hex, created_at, updated_at)
VALUES (?, ?, ?, ?)
`, [account.did, account.publicKeyHex, account.createdAt, account.updatedAt]);
} catch (error) {
if (error.message.includes('UNIQUE constraint failed')) {
// Handle duplicate key
}
}
```
### 2. Transaction Recovery
```typescript
// Dexie transaction
try {
await db.transaction('rw', db.accounts, async () => {
// Operations
});
} catch (error) {
// Dexie automatically rolls back
}
// absurd-sql transaction
try {
await db.exec('BEGIN TRANSACTION;');
// Operations
await db.exec('COMMIT;');
} catch (error) {
await db.exec('ROLLBACK;');
throw error;
}
```
## Migration Strategy
1. **Preparation**
- Export all Dexie data
- Verify data integrity
- Create SQLite schema
- Setup indexes
2. **Migration**
- Import data in transactions
- Verify each batch
- Handle errors gracefully
- Maintain backup
3. **Verification**
- Compare record counts
- Verify data integrity
- Test common queries
- Validate relationships
4. **Cleanup**
- Remove Dexie database
- Clear IndexedDB storage
- Update application code
- Remove old dependencies

View File

@@ -1,272 +0,0 @@
# Migration Fence Definition: Dexie to SQLite
## Overview
This document defines the **migration fence** - the boundary between the legacy Dexie (IndexedDB) storage system and the new SQLite-based storage system in TimeSafari. The fence ensures controlled migration while maintaining data integrity and application stability.
## Current Migration Status
### ✅ Completed Components
- **SQLite Database Service**: Fully implemented with absurd-sql
- **Platform Service Layer**: Unified database interface across platforms
- **Migration Tools**: Data comparison and transfer utilities
- **Schema Migration**: Complete table structure migration
- **Data Export/Import**: Backup and restore functionality
### 🔄 Active Migration Components
- **Settings Migration**: Core user settings transferred
- **Account Migration**: Identity and key management
- **Contact Migration**: User contact data (via import interface)
### ❌ Legacy Components (Fence Boundary)
- **Dexie Database**: Legacy IndexedDB storage (disabled by default)
- **Dexie-Specific Code**: Direct database access patterns
- **Legacy Migration Paths**: Old data transfer methods
## Migration Fence Definition
### 1. Configuration Boundary
```typescript
// src/constants/app.ts
export const USE_DEXIE_DB = false; // FENCE: Controls legacy database access
```
**Fence Rule**: When `USE_DEXIE_DB = false`:
- All new data operations use SQLite
- Legacy Dexie database is not initialized
- Migration tools are the only path to legacy data
**Fence Rule**: When `USE_DEXIE_DB = true`:
- Legacy database is available for migration
- Dual-write operations may be enabled
- Migration tools can access both databases
### 2. Service Layer Boundary
```typescript
// src/services/PlatformServiceFactory.ts
export class PlatformServiceFactory {
public static getInstance(): PlatformService {
// FENCE: All database operations go through platform service
// No direct Dexie access outside migration tools
}
}
```
**Fence Rule**: All database operations must use:
- `PlatformService.dbQuery()` for read operations
- `PlatformService.dbExec()` for write operations
- No direct `db.` or `accountsDBPromise` access in application code
### 3. Data Access Patterns
#### ✅ Allowed (Inside Fence)
```typescript
// Use platform service for all database operations
const platformService = PlatformServiceFactory.getInstance();
const contacts = await platformService.dbQuery(
"SELECT * FROM contacts WHERE did = ?",
[accountDid]
);
```
#### ❌ Forbidden (Outside Fence)
```typescript
// Direct Dexie access (legacy pattern)
const contacts = await db.contacts.where('did').equals(accountDid).toArray();
// Direct database reference
const result = await accountsDBPromise;
```
### 4. Migration Tool Boundary
```typescript
// src/services/indexedDBMigrationService.ts
// FENCE: Only migration tools can access both databases
export async function compareDatabases(): Promise<DataComparison> {
// This is the ONLY place where both databases are accessed
}
```
**Fence Rule**: Migration tools are the exclusive interface between:
- Legacy Dexie database
- New SQLite database
- Data comparison and transfer operations
## Migration Fence Guidelines
### 1. Code Development Rules
#### New Feature Development
- **Always** use `PlatformService` for database operations
- **Never** import or reference Dexie directly
- **Always** test with `USE_DEXIE_DB = false`
#### Legacy Code Maintenance
- **Only** modify Dexie code for migration purposes
- **Always** add migration tests for schema changes
- **Never** add new Dexie-specific features
### 2. Data Integrity Rules
#### Migration Safety
- **Always** create backups before migration
- **Always** verify data integrity after migration
- **Never** delete legacy data until verified
#### Rollback Strategy
- **Always** maintain ability to rollback to Dexie
- **Always** preserve migration logs
- **Never** assume migration is irreversible
### 3. Testing Requirements
#### Migration Testing
```typescript
// Required test pattern for migration
describe('Database Migration', () => {
it('should migrate data without loss', async () => {
// 1. Enable Dexie
// 2. Create test data
// 3. Run migration
// 4. Verify data integrity
// 5. Disable Dexie
});
});
```
#### Application Testing
```typescript
// Required test pattern for application features
describe('Feature with Database', () => {
it('should work with SQLite only', async () => {
// Test with USE_DEXIE_DB = false
// Verify all operations use PlatformService
});
});
```
## Migration Fence Enforcement
### 1. Static Analysis
#### ESLint Rules
```json
{
"rules": {
"no-restricted-imports": [
"error",
{
"patterns": [
{
"group": ["../db/index"],
"message": "Use PlatformService instead of direct Dexie access"
}
]
}
]
}
}
```
#### TypeScript Rules
```json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true
}
}
```
### 2. Runtime Checks
#### Development Mode Validation
```typescript
// Development-only fence validation
if (import.meta.env.DEV && USE_DEXIE_DB) {
console.warn('⚠️ Dexie is enabled - migration mode active');
}
```
#### Production Safety
```typescript
// Production fence enforcement
if (import.meta.env.PROD && USE_DEXIE_DB) {
throw new Error('Dexie cannot be enabled in production');
}
```
## Migration Fence Timeline
### Phase 1: Fence Establishment ✅
- [x] Define migration fence boundaries
- [x] Implement PlatformService layer
- [x] Create migration tools
- [x] Set `USE_DEXIE_DB = false` by default
### Phase 2: Data Migration 🔄
- [x] Migrate core settings
- [x] Migrate account data
- [ ] Complete contact migration
- [ ] Verify all data integrity
### Phase 3: Code Cleanup 📋
- [ ] Remove unused Dexie imports
- [ ] Clean up legacy database code
- [ ] Update all documentation
- [ ] Remove migration tools
### Phase 4: Fence Removal 🎯
- [ ] Remove `USE_DEXIE_DB` constant
- [ ] Remove Dexie dependencies
- [ ] Remove migration service
- [ ] Finalize SQLite-only architecture
## Security Considerations
### 1. Data Protection
- **Encryption**: Maintain encryption standards across migration
- **Access Control**: Preserve user privacy during migration
- **Audit Trail**: Log all migration operations
### 2. Error Handling
- **Graceful Degradation**: Handle migration failures gracefully
- **User Communication**: Clear messaging about migration status
- **Recovery Options**: Provide rollback mechanisms
## Performance Considerations
### 1. Migration Performance
- **Batch Operations**: Use transactions for bulk data transfer
- **Progress Indicators**: Show migration progress to users
- **Background Processing**: Non-blocking migration operations
### 2. Application Performance
- **Query Optimization**: Optimize SQLite queries for performance
- **Indexing Strategy**: Maintain proper database indexes
- **Memory Management**: Efficient memory usage during migration
## Documentation Requirements
### 1. Code Documentation
- **Migration Fence Comments**: Document fence boundaries in code
- **API Documentation**: Update all database API documentation
- **Migration Guides**: Comprehensive migration documentation
### 2. User Documentation
- **Migration Instructions**: Clear user migration steps
- **Troubleshooting**: Common migration issues and solutions
- **Rollback Instructions**: How to revert if needed
## Conclusion
The migration fence provides a controlled boundary between legacy and new database systems, ensuring:
- **Data Integrity**: No data loss during migration
- **Application Stability**: Consistent behavior across platforms
- **Development Clarity**: Clear guidelines for code development
- **Migration Safety**: Controlled and reversible migration process
This fence will remain in place until all data is successfully migrated and verified, at which point the legacy system can be safely removed.

View File

@@ -1,355 +0,0 @@
# Database Migration Security Audit Checklist
## Overview
This document provides a comprehensive security audit checklist for the Dexie to SQLite migration in TimeSafari. The checklist ensures that data protection, privacy, and security are maintained throughout the migration process.
## Pre-Migration Security Assessment
### 1. Data Classification and Sensitivity
- [ ] **Data Inventory**
- [ ] Identify all sensitive data types (DIDs, private keys, personal information)
- [ ] Document data retention requirements
- [ ] Map data relationships and dependencies
- [ ] Assess data sensitivity levels (public, internal, confidential, restricted)
- [ ] **Encryption Assessment**
- [ ] Verify current encryption methods for sensitive data
- [ ] Document encryption keys and their management
- [ ] Assess encryption strength and compliance
- [ ] Plan encryption migration strategy
### 2. Access Control Review
- [ ] **User Access Rights**
- [ ] Audit current user permissions and roles
- [ ] Document access control mechanisms
- [ ] Verify principle of least privilege
- [ ] Plan access control migration
- [ ] **System Access**
- [ ] Review database access patterns
- [ ] Document authentication mechanisms
- [ ] Assess session management
- [ ] Plan authentication migration
### 3. Compliance Requirements
- [ ] **Regulatory Compliance**
- [ ] Identify applicable regulations (GDPR, CCPA, etc.)
- [ ] Document data processing requirements
- [ ] Assess privacy impact
- [ ] Plan compliance verification
- [ ] **Industry Standards**
- [ ] Review security standards compliance
- [ ] Document security controls
- [ ] Assess audit requirements
- [ ] Plan standards compliance
## Migration Security Controls
### 1. Data Protection During Migration
- [ ] **Encryption in Transit**
- [ ] Verify all data transfers are encrypted
- [ ] Use secure communication protocols (TLS 1.3+)
- [ ] Implement secure API endpoints
- [ ] Monitor encryption status
- [ ] **Encryption at Rest**
- [ ] Maintain encryption for stored data
- [ ] Verify encryption key management
- [ ] Test encryption/decryption processes
- [ ] Document encryption procedures
### 2. Access Control During Migration
- [ ] **Authentication**
- [ ] Maintain user authentication during migration
- [ ] Verify session management
- [ ] Implement secure token handling
- [ ] Monitor authentication events
- [ ] **Authorization**
- [ ] Preserve user permissions during migration
- [ ] Verify role-based access control
- [ ] Implement audit logging
- [ ] Monitor access patterns
### 3. Data Integrity
- [ ] **Data Validation**
- [ ] Implement input validation for all data
- [ ] Verify data format consistency
- [ ] Test data transformation processes
- [ ] Document validation rules
- [ ] **Data Verification**
- [ ] Implement checksums for data integrity
- [ ] Verify data completeness after migration
- [ ] Test data consistency checks
- [ ] Document verification procedures
## Migration Process Security
### 1. Backup Security
- [ ] **Backup Creation**
- [ ] Create encrypted backups before migration
- [ ] Verify backup integrity
- [ ] Store backups securely
- [ ] Test backup restoration
- [ ] **Backup Access**
- [ ] Limit backup access to authorized personnel
- [ ] Implement backup access logging
- [ ] Verify backup encryption
- [ ] Document backup procedures
### 2. Migration Tool Security
- [ ] **Tool Authentication**
- [ ] Implement secure authentication for migration tools
- [ ] Verify tool access controls
- [ ] Monitor tool usage
- [ ] Document tool security
- [ ] **Tool Validation**
- [ ] Verify migration tool integrity
- [ ] Test tool security features
- [ ] Validate tool outputs
- [ ] Document tool validation
### 3. Error Handling
- [ ] **Error Security**
- [ ] Implement secure error handling
- [ ] Avoid information disclosure in errors
- [ ] Log security-relevant errors
- [ ] Document error procedures
- [ ] **Recovery Security**
- [ ] Implement secure recovery procedures
- [ ] Verify recovery data protection
- [ ] Test recovery processes
- [ ] Document recovery security
## Post-Migration Security
### 1. Data Verification
- [ ] **Data Completeness**
- [ ] Verify all data was migrated successfully
- [ ] Check for data corruption
- [ ] Validate data relationships
- [ ] Document verification results
- [ ] **Data Accuracy**
- [ ] Verify data accuracy after migration
- [ ] Test data consistency
- [ ] Validate data integrity
- [ ] Document accuracy checks
### 2. Access Control Verification
- [ ] **User Access**
- [ ] Verify user access rights after migration
- [ ] Test authentication mechanisms
- [ ] Validate authorization rules
- [ ] Document access verification
- [ ] **System Access**
- [ ] Verify system access controls
- [ ] Test API security
- [ ] Validate session management
- [ ] Document system security
### 3. Security Testing
- [ ] **Penetration Testing**
- [ ] Conduct security penetration testing
- [ ] Test for common vulnerabilities
- [ ] Verify security controls
- [ ] Document test results
- [ ] **Vulnerability Assessment**
- [ ] Scan for security vulnerabilities
- [ ] Assess security posture
- [ ] Identify security gaps
- [ ] Document assessment results
## Monitoring and Logging
### 1. Security Monitoring
- [ ] **Access Monitoring**
- [ ] Monitor database access patterns
- [ ] Track user authentication events
- [ ] Monitor system access
- [ ] Document monitoring procedures
- [ ] **Data Monitoring**
- [ ] Monitor data access patterns
- [ ] Track data modification events
- [ ] Monitor data integrity
- [ ] Document data monitoring
### 2. Security Logging
- [ ] **Audit Logging**
- [ ] Implement comprehensive audit logging
- [ ] Log all security-relevant events
- [ ] Secure log storage and access
- [ ] Document logging procedures
- [ ] **Log Analysis**
- [ ] Implement log analysis tools
- [ ] Monitor for security incidents
- [ ] Analyze security trends
- [ ] Document analysis procedures
## Incident Response
### 1. Security Incident Planning
- [ ] **Incident Response Plan**
- [ ] Develop security incident response plan
- [ ] Define incident response procedures
- [ ] Train incident response team
- [ ] Document response procedures
- [ ] **Incident Detection**
- [ ] Implement incident detection mechanisms
- [ ] Monitor for security incidents
- [ ] Establish incident reporting procedures
- [ ] Document detection procedures
### 2. Recovery Procedures
- [ ] **Data Recovery**
- [ ] Develop data recovery procedures
- [ ] Test recovery processes
- [ ] Verify recovery data integrity
- [ ] Document recovery procedures
- [ ] **System Recovery**
- [ ] Develop system recovery procedures
- [ ] Test system recovery
- [ ] Verify system security after recovery
- [ ] Document recovery procedures
## Compliance Verification
### 1. Regulatory Compliance
- [ ] **Privacy Compliance**
- [ ] Verify GDPR compliance
- [ ] Check CCPA compliance
- [ ] Assess other privacy regulations
- [ ] Document compliance status
- [ ] **Security Compliance**
- [ ] Verify security standard compliance
- [ ] Check industry requirements
- [ ] Assess security certifications
- [ ] Document compliance status
### 2. Audit Requirements
- [ ] **Audit Trail**
- [ ] Maintain comprehensive audit trail
- [ ] Verify audit log integrity
- [ ] Test audit log accessibility
- [ ] Document audit procedures
- [ ] **Audit Reporting**
- [ ] Generate audit reports
- [ ] Verify report accuracy
- [ ] Distribute reports securely
- [ ] Document reporting procedures
## Documentation and Training
### 1. Security Documentation
- [ ] **Security Procedures**
- [ ] Document security procedures
- [ ] Update security policies
- [ ] Create security guidelines
- [ ] Maintain documentation
- [ ] **Security Training**
- [ ] Develop security training materials
- [ ] Train staff on security procedures
- [ ] Verify training effectiveness
- [ ] Document training procedures
### 2. Ongoing Security
- [ ] **Security Maintenance**
- [ ] Establish security maintenance procedures
- [ ] Schedule security updates
- [ ] Monitor security trends
- [ ] Document maintenance procedures
- [ ] **Security Review**
- [ ] Conduct regular security reviews
- [ ] Update security controls
- [ ] Assess security effectiveness
- [ ] Document review procedures
## Risk Assessment
### 1. Risk Identification
- [ ] **Security Risks**
- [ ] Identify potential security risks
- [ ] Assess risk likelihood and impact
- [ ] Prioritize security risks
- [ ] Document risk assessment
- [ ] **Mitigation Strategies**
- [ ] Develop risk mitigation strategies
- [ ] Implement risk controls
- [ ] Monitor risk status
- [ ] Document mitigation procedures
### 2. Risk Monitoring
- [ ] **Risk Tracking**
- [ ] Track identified risks
- [ ] Monitor risk status
- [ ] Update risk assessments
- [ ] Document risk tracking
- [ ] **Risk Reporting**
- [ ] Generate risk reports
- [ ] Distribute risk information
- [ ] Update risk documentation
- [ ] Document reporting procedures
## Conclusion
This security audit checklist ensures that the database migration maintains the highest standards of data protection, privacy, and security. Regular review and updates of this checklist are essential to maintain security throughout the migration process and beyond.
### Security Checklist Summary
- [ ] **Pre-Migration Assessment**: Complete
- [ ] **Migration Controls**: Complete
- [ ] **Process Security**: Complete
- [ ] **Post-Migration Verification**: Complete
- [ ] **Monitoring and Logging**: Complete
- [ ] **Incident Response**: Complete
- [ ] **Compliance Verification**: Complete
- [ ] **Documentation and Training**: Complete
- [ ] **Risk Assessment**: Complete
**Overall Security Status**: [ ] Secure [ ] Needs Attention [ ] Critical Issues
**Next Review Date**: _______________
**Reviewed By**: _______________
**Approved By**: _______________

View File

@@ -1,226 +0,0 @@
# Migration Guide: Dexie to absurd-sql
## Overview
This document outlines the migration process from Dexie.js to absurd-sql for the TimeSafari app's storage implementation. The migration aims to provide a consistent SQLite-based storage solution across all platforms while maintaining data integrity and ensuring a smooth transition for users.
**Current Status**: The migration is in **Phase 2** with a well-defined migration fence in place. Core settings and account data have been migrated, with contact migration in progress. **ActiveDid migration has been implemented** to ensure user identity continuity.
## Migration Goals
1. **Data Integrity**
- Preserve all existing data
- Maintain data relationships
- Ensure data consistency
- **Preserve user's active identity**
2. **Performance**
- Improve query performance
- Reduce storage overhead
- Optimize for platform-specific capabilities
3. **User Experience**
- Seamless transition with no data loss
- Maintain user's active identity and preferences
- Preserve application state
## Migration Architecture
### Migration Fence
The migration fence is defined by the `USE_DEXIE_DB` constant in `src/constants/app.ts`:
- `USE_DEXIE_DB = false` (default): Uses SQLite database
- `USE_DEXIE_DB = true`: Uses Dexie database (for migration purposes)
### Migration Order
The migration follows a specific order to maintain data integrity:
1. **Accounts** (foundational - contains DIDs)
2. **Settings** (references accountDid, activeDid)
3. **ActiveDid** (depends on accounts and settings) ⭐ **NEW**
4. **Contacts** (independent, but migrated after accounts for consistency)
## ActiveDid Migration ⭐ **NEW FEATURE**
### Problem Solved
Previously, the `activeDid` setting was not migrated from Dexie to SQLite, causing users to lose their active identity after migration.
### Solution Implemented
The migration now includes a dedicated step for migrating the `activeDid`:
1. **Detection**: Identifies the `activeDid` from Dexie master settings
2. **Validation**: Verifies the `activeDid` exists in SQLite accounts
3. **Migration**: Updates SQLite master settings with the `activeDid`
4. **Error Handling**: Graceful handling of missing accounts
### Implementation Details
#### New Function: `migrateActiveDid()`
```typescript
export async function migrateActiveDid(): Promise<MigrationResult> {
// 1. Get Dexie settings to find the activeDid
const dexieSettings = await getDexieSettings();
const masterSettings = dexieSettings.find(setting => !setting.accountDid);
// 2. Verify the activeDid exists in SQLite accounts
const accountExists = await platformService.dbQuery(
"SELECT did FROM accounts WHERE did = ?",
[dexieActiveDid],
);
// 3. Update SQLite master settings
await updateDefaultSettings({ activeDid: dexieActiveDid });
}
```
#### Enhanced `migrateSettings()` Function
The settings migration now includes activeDid handling:
- Extracts `activeDid` from Dexie master settings
- Validates account existence in SQLite
- Updates SQLite master settings with the `activeDid`
#### Updated `migrateAll()` Function
The complete migration now includes a dedicated step for activeDid:
```typescript
// Step 3: Migrate ActiveDid (depends on accounts and settings)
logger.info("[MigrationService] Step 3: Migrating activeDid...");
const activeDidResult = await migrateActiveDid();
```
### Benefits
-**User Identity Preservation**: Users maintain their active identity
-**Seamless Experience**: No need to manually select identity after migration
-**Data Consistency**: Ensures all identity-related settings are preserved
-**Error Resilience**: Graceful handling of edge cases
## Migration Process
### Phase 1: Preparation ✅
- [x] Enable Dexie database access
- [x] Implement data comparison tools
- [x] Create migration service structure
### Phase 2: Core Migration ✅
- [x] Account migration with `importFromMnemonic`
- [x] Settings migration (excluding activeDid)
- [x] **ActiveDid migration****COMPLETED**
- [x] Contact migration framework
### Phase 3: Validation and Cleanup 🔄
- [ ] Comprehensive data validation
- [ ] Performance testing
- [ ] User acceptance testing
- [ ] Dexie removal
## Usage
### Manual Migration
```typescript
import { migrateAll, migrateActiveDid } from '../services/indexedDBMigrationService';
// Complete migration
const result = await migrateAll();
// Or migrate just the activeDid
const activeDidResult = await migrateActiveDid();
```
### Migration Verification
```typescript
import { compareDatabases } from '../services/indexedDBMigrationService';
const comparison = await compareDatabases();
console.log('Migration differences:', comparison.differences);
```
## Error Handling
### ActiveDid Migration Errors
- **Missing Account**: If the `activeDid` from Dexie doesn't exist in SQLite accounts
- **Database Errors**: Connection or query failures
- **Settings Update Failures**: Issues updating SQLite master settings
### Recovery Strategies
1. **Automatic Recovery**: Migration continues even if activeDid migration fails
2. **Manual Recovery**: Users can manually select their identity after migration
3. **Fallback**: System creates new identity if none exists
## Security Considerations
### Data Protection
- All sensitive data (mnemonics, private keys) are encrypted
- Migration preserves encryption standards
- No plaintext data exposure during migration
### Identity Verification
- ActiveDid migration validates account existence
- Prevents setting non-existent identities as active
- Maintains cryptographic integrity
## Testing
### Migration Testing
```bash
# Enable Dexie for testing
# Set USE_DEXIE_DB = true in constants/app.ts
# Run migration
npm run migrate
# Verify results
npm run test:migration
```
### ActiveDid Testing
```typescript
// Test activeDid migration specifically
const result = await migrateActiveDid();
expect(result.success).toBe(true);
expect(result.warnings).toContain('Successfully migrated activeDid');
```
## Troubleshooting
### Common Issues
1. **ActiveDid Not Found**
- Ensure accounts were migrated before activeDid migration
- Check that the Dexie activeDid exists in SQLite accounts
2. **Migration Failures**
- Verify Dexie database is accessible
- Check SQLite database permissions
- Review migration logs for specific errors
3. **Data Inconsistencies**
- Use `compareDatabases()` to identify differences
- Re-run migration if necessary
- Check for duplicate or conflicting records
### Debugging
```typescript
// Enable detailed logging
logger.setLevel('debug');
// Check migration status
const comparison = await compareDatabases();
console.log('Settings differences:', comparison.differences.settings);
```
## Future Enhancements
### Planned Improvements
1. **Batch Processing**: Optimize for large datasets
2. **Incremental Migration**: Support partial migrations
3. **Rollback Capability**: Ability to revert migration
4. **Progress Tracking**: Real-time migration progress
### Performance Optimizations
1. **Parallel Processing**: Migrate independent data concurrently
2. **Memory Management**: Optimize for large datasets
3. **Transaction Batching**: Reduce database round trips
## Conclusion
The Dexie to SQLite migration provides a robust, secure, and user-friendly transition path. The addition of activeDid migration ensures that users maintain their identity continuity throughout the migration process, significantly improving the user experience.
The migration fence architecture allows for controlled, reversible migration while maintaining application stability and data integrity.

View File

@@ -1,339 +0,0 @@
# Secure Storage Implementation Guide for TimeSafari App
## Overview
This document outlines the implementation of secure storage for the TimeSafari app. The implementation focuses on:
1. **Platform-Specific Storage Solutions**:
- Web: SQLite with IndexedDB backend (absurd-sql)
- Electron: SQLite with Node.js backend
- Native: (Planned) SQLCipher with platform-specific secure storage
2. **Key Features**:
- SQLite-based storage using absurd-sql for web
- Platform-specific service factory pattern
- Consistent API across platforms
- Migration support from Dexie.js
## Quick Start
### 1. Installation
```bash
# Core dependencies
npm install @jlongster/sql.js
npm install absurd-sql
# Platform-specific dependencies (for future native support)
npm install @capacitor/preferences
npm install @capacitor-community/biometric-auth
```
### 2. Basic Usage
```typescript
// Using the platform service
import { PlatformServiceFactory } from '../services/PlatformServiceFactory';
// Get platform-specific service instance
const platformService = PlatformServiceFactory.getInstance();
// Example database operations
async function example() {
try {
// Query example
const result = await platformService.dbQuery(
"SELECT * FROM accounts WHERE did = ?",
[did]
);
// Execute example
await platformService.dbExec(
"INSERT INTO accounts (did, public_key_hex) VALUES (?, ?)",
[did, publicKeyHex]
);
} catch (error) {
console.error('Database operation failed:', error);
}
}
```
### 3. Platform Detection
```typescript
// src/services/PlatformServiceFactory.ts
export class PlatformServiceFactory {
static getInstance(): PlatformService {
if (process.env.ELECTRON) {
// Electron platform
return new ElectronPlatformService();
} else {
// Web platform (default)
return new AbsurdSqlDatabaseService();
}
}
}
```
### 4. Current Implementation Details
#### Web Platform (AbsurdSqlDatabaseService)
The web platform uses absurd-sql with IndexedDB backend:
```typescript
// src/services/AbsurdSqlDatabaseService.ts
export class AbsurdSqlDatabaseService implements PlatformService {
private static instance: AbsurdSqlDatabaseService | null = null;
private db: AbsurdSqlDatabase | null = null;
private initialized: boolean = false;
// Singleton pattern
static getInstance(): AbsurdSqlDatabaseService {
if (!AbsurdSqlDatabaseService.instance) {
AbsurdSqlDatabaseService.instance = new AbsurdSqlDatabaseService();
}
return AbsurdSqlDatabaseService.instance;
}
// Database operations
async dbQuery(sql: string, params: unknown[] = []): Promise<QueryExecResult[]> {
await this.waitForInitialization();
return this.queueOperation<QueryExecResult[]>("query", sql, params);
}
async dbExec(sql: string, params: unknown[] = []): Promise<void> {
await this.waitForInitialization();
await this.queueOperation<void>("run", sql, params);
}
}
```
Key features:
- Uses absurd-sql for SQLite in the browser
- Implements operation queuing for thread safety
- Handles initialization and connection management
- Provides consistent API across platforms
### 5. Migration from Dexie.js
The current implementation supports gradual migration from Dexie.js:
```typescript
// Example of dual-storage pattern
async function getAccount(did: string): Promise<Account | undefined> {
// Try SQLite first
const platform = PlatformServiceFactory.getInstance();
let account = await platform.dbQuery(
"SELECT * FROM accounts WHERE did = ?",
[did]
);
// Fallback to Dexie if needed
if (USE_DEXIE_DB) {
account = await db.accounts.get(did);
}
return account;
}
```
#### A. Modifying Code
When converting from Dexie.js to SQL-based implementation, follow these patterns:
1. **Database Access Pattern**
```typescript
// Before (Dexie)
const result = await db.table.where("field").equals(value).first();
// After (SQL)
const platform = PlatformServiceFactory.getInstance();
let result = await platform.dbQuery(
"SELECT * FROM table WHERE field = ?",
[value]
);
result = databaseUtil.mapQueryResultToValues(result);
// Fallback to Dexie if needed
if (USE_DEXIE_DB) {
result = await db.table.where("field").equals(value).first();
}
```
2. **Update Operations**
```typescript
// Before (Dexie)
await db.table.where("id").equals(id).modify(changes);
// After (SQL)
// For settings updates, use the utility methods:
await databaseUtil.updateDefaultSettings(changes);
// OR
await databaseUtil.updateAccountSettings(did, changes);
// For other tables, use direct SQL:
const platform = PlatformServiceFactory.getInstance();
await platform.dbExec(
"UPDATE table SET field1 = ?, field2 = ? WHERE id = ?",
[changes.field1, changes.field2, id]
);
// Fallback to Dexie if needed
if (USE_DEXIE_DB) {
await db.table.where("id").equals(id).modify(changes);
}
```
3. **Insert Operations**
```typescript
// Before (Dexie)
await db.table.add(item);
// After (SQL)
const platform = PlatformServiceFactory.getInstance();
const columns = Object.keys(item);
const values = Object.values(item);
const placeholders = values.map(() => '?').join(', ');
const sql = `INSERT INTO table (${columns.join(', ')}) VALUES (${placeholders})`;
await platform.dbExec(sql, values);
// Fallback to Dexie if needed
if (USE_DEXIE_DB) {
await db.table.add(item);
}
```
4. **Delete Operations**
```typescript
// Before (Dexie)
await db.table.where("id").equals(id).delete();
// After (SQL)
const platform = PlatformServiceFactory.getInstance();
await platform.dbExec("DELETE FROM table WHERE id = ?", [id]);
// Fallback to Dexie if needed
if (USE_DEXIE_DB) {
await db.table.where("id").equals(id).delete();
}
```
5. **Result Processing**
```typescript
// Before (Dexie)
const items = await db.table.toArray();
// After (SQL)
const platform = PlatformServiceFactory.getInstance();
let items = await platform.dbQuery("SELECT * FROM table");
items = databaseUtil.mapQueryResultToValues(items);
// Fallback to Dexie if needed
if (USE_DEXIE_DB) {
items = await db.table.toArray();
}
```
6. **Using Utility Methods**
When working with settings or other common operations, use the utility methods in `db/index.ts`:
```typescript
// Settings operations
await databaseUtil.updateDefaultSettings(settings);
await databaseUtil.updateAccountSettings(did, settings);
const settings = await databaseUtil.retrieveSettingsForDefaultAccount();
const settings = await databaseUtil.retrieveSettingsForActiveAccount();
// Logging operations
await databaseUtil.logToDb(message);
await databaseUtil.logConsoleAndDb(message, showInConsole);
```
Key Considerations:
- Always use `databaseUtil.mapQueryResultToValues()` to process SQL query results
- Use utility methods from `db/index.ts` when available instead of direct SQL
- Keep Dexie fallbacks wrapped in `if (USE_DEXIE_DB)` checks
- For queries that return results, use `let` variables to allow Dexie fallback to override
- For updates/inserts/deletes, execute both SQL and Dexie operations when `USE_DEXIE_DB` is true
Example Migration:
```typescript
// Before (Dexie)
export async function updateSettings(settings: Settings): Promise<void> {
await db.settings.put(settings);
}
// After (SQL)
export async function updateSettings(settings: Settings): Promise<void> {
const platform = PlatformServiceFactory.getInstance();
const { sql, params } = generateUpdateStatement(
settings,
"settings",
"id = ?",
[settings.id]
);
await platform.dbExec(sql, params);
}
```
Remember to:
- Create database access code to use the platform service, putting it in front of the Dexie version
- Instead of removing Dexie-specific code, keep it.
- For creates & updates & deletes, the duplicate code is fine.
- For queries where we use the results, make the setting from SQL into a 'let' variable, then wrap the Dexie code in a check for USE_DEXIE_DB from app.ts and if
it's true then use that result instead of the SQL code's result.
- Consider data migration needs, and warn if there are any potential migration problems
## Success Criteria
1. **Functionality**
- [x] Basic CRUD operations work correctly
- [x] Platform service factory pattern implemented
- [x] Error handling in place
- [ ] Native platform support (planned)
2. **Performance**
- [x] Database operations complete within acceptable time
- [x] Operation queuing for thread safety
- [x] Proper initialization handling
- [ ] Performance monitoring (planned)
3. **Security**
- [x] Basic data integrity
- [ ] Encryption (planned for native platforms)
- [ ] Secure key storage (planned)
- [ ] Platform-specific security features (planned)
4. **Testing**
- [x] Basic unit tests
- [ ] Comprehensive integration tests (planned)
- [ ] Platform-specific tests (planned)
- [ ] Migration tests (planned)
## Next Steps
1. **Native Platform Support**
- Implement SQLCipher for iOS/Android
- Add platform-specific secure storage
- Implement biometric authentication
2. **Enhanced Security**
- Add encryption for sensitive data
- Implement secure key storage
- Add platform-specific security features
3. **Testing and Monitoring**
- Add comprehensive test coverage
- Implement performance monitoring
- Add error tracking and analytics
4. **Documentation**
- Add API documentation
- Create migration guides
- Document security measures

View File

@@ -1,329 +0,0 @@
# Storage Implementation Checklist
## Core Services
### 1. Storage Service Layer
- [x] Create base `PlatformService` interface
- [x] Define common methods for all platforms
- [x] Add platform-specific method signatures
- [x] Include error handling types
- [x] Add migration support methods
- [x] Implement platform-specific services
- [x] `AbsurdSqlDatabaseService` (web)
- [x] Database initialization
- [x] VFS setup with IndexedDB backend
- [x] Connection management
- [x] Operation queuing
- [ ] `NativeSQLiteService` (iOS/Android) (planned)
- [ ] SQLCipher integration
- [ ] Native bridge setup
- [ ] File system access
- [ ] `ElectronSQLiteService` (planned)
- [ ] Node SQLite integration
- [ ] IPC communication
- [ ] File system access
### 2. Migration Services
- [x] Implement basic migration support
- [x] Dual-storage pattern (SQLite + Dexie)
- [x] Basic data verification
- [ ] Rollback procedures (planned)
- [ ] Progress tracking (planned)
- [ ] Create `MigrationUI` components (planned)
- [ ] Progress indicators
- [ ] Error handling
- [ ] User notifications
- [ ] Manual triggers
### 3. Security Layer
- [x] Basic data integrity
- [ ] Implement `EncryptionService` (planned)
- [ ] Key management
- [ ] Encryption/decryption
- [ ] Secure storage
- [ ] Add `BiometricService` (planned)
- [ ] Platform detection
- [ ] Authentication flow
- [ ] Fallback mechanisms
## Platform-Specific Implementation
### Web Platform
- [x] Setup absurd-sql
- [x] Install dependencies
```json
{
"@jlongster/sql.js": "^1.8.0",
"absurd-sql": "^1.8.0"
}
```
- [x] Configure VFS with IndexedDB backend
- [x] Setup worker threads
- [x] Implement operation queuing
- [x] Configure database pragmas
```sql
PRAGMA journal_mode=MEMORY;
PRAGMA synchronous=NORMAL;
PRAGMA foreign_keys=ON;
PRAGMA busy_timeout=5000;
```
- [x] Update build configuration
- [x] Modify `vite.config.ts`
- [x] Add worker configuration
- [x] Update chunk splitting
- [x] Configure asset handling
- [x] Implement IndexedDB backend
- [x] Create database service
- [x] Add operation queuing
- [x] Handle initialization
- [x] Implement atomic operations
### iOS Platform (Planned)
- [ ] Setup SQLCipher
- [ ] Install pod dependencies
- [ ] Configure encryption
- [ ] Setup keychain access
- [ ] Implement secure storage
- [ ] Update Capacitor config
- [ ] Modify `capacitor.config.ts`
- [ ] Add iOS permissions
- [ ] Configure backup
- [ ] Setup app groups
### Android Platform (Planned)
- [ ] Setup SQLCipher
- [ ] Add Gradle dependencies
- [ ] Configure encryption
- [ ] Setup keystore
- [ ] Implement secure storage
- [ ] Update Capacitor config
- [ ] Modify `capacitor.config.ts`
- [ ] Add Android permissions
- [ ] Configure backup
- [ ] Setup file provider
### Electron Platform (Planned)
- [ ] Setup Node SQLite
- [ ] Install dependencies
- [ ] Configure IPC
- [ ] Setup file system access
- [ ] Implement secure storage
- [ ] Update Electron config
- [ ] Modify `electron.config.ts`
- [ ] Add security policies
- [ ] Configure file access
- [ ] Setup auto-updates
## Data Models and Types
### 1. Database Schema
- [x] Define tables
```sql
-- Accounts table
CREATE TABLE accounts (
did TEXT PRIMARY KEY,
public_key_hex TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
-- Settings table
CREATE TABLE settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at INTEGER NOT NULL
);
-- Contacts table
CREATE TABLE contacts (
id TEXT PRIMARY KEY,
did TEXT NOT NULL,
name TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
FOREIGN KEY (did) REFERENCES accounts(did)
);
-- Indexes for performance
CREATE INDEX idx_accounts_created_at ON accounts(created_at);
CREATE INDEX idx_contacts_did ON contacts(did);
CREATE INDEX idx_settings_updated_at ON settings(updated_at);
```
- [x] Create indexes
- [x] Define constraints
- [ ] Add triggers (planned)
- [ ] Setup migrations (planned)
### 2. Type Definitions
- [x] Create interfaces
```typescript
interface Account {
did: string;
publicKeyHex: string;
createdAt: number;
updatedAt: number;
}
interface Setting {
key: string;
value: string;
updatedAt: number;
}
interface Contact {
id: string;
did: string;
name?: string;
createdAt: number;
updatedAt: number;
}
```
- [x] Add validation
- [x] Create DTOs
- [x] Define enums
- [x] Add type guards
## UI Components
### 1. Migration UI (Planned)
- [ ] Create components
- [ ] `MigrationProgress.vue`
- [ ] `MigrationError.vue`
- [ ] `MigrationSettings.vue`
- [ ] `MigrationStatus.vue`
### 2. Settings UI (Planned)
- [ ] Update components
- [ ] Add storage settings
- [ ] Add migration controls
- [ ] Add backup options
- [ ] Add security settings
### 3. Error Handling UI (Planned)
- [ ] Create components
- [ ] `StorageError.vue`
- [ ] `QuotaExceeded.vue`
- [ ] `MigrationFailed.vue`
- [ ] `RecoveryOptions.vue`
## Testing
### 1. Unit Tests
- [x] Basic service tests
- [x] Platform service tests
- [x] Database operation tests
- [ ] Security service tests (planned)
- [ ] Platform detection tests (planned)
### 2. Integration Tests (Planned)
- [ ] Test migrations
- [ ] Web platform tests
- [ ] iOS platform tests
- [ ] Android platform tests
- [ ] Electron platform tests
### 3. E2E Tests (Planned)
- [ ] Test workflows
- [ ] Account management
- [ ] Settings management
- [ ] Contact management
- [ ] Migration process
## Documentation
### 1. Technical Documentation
- [x] Update architecture docs
- [x] Add API documentation
- [ ] Create migration guides (planned)
- [ ] Document security measures (planned)
### 2. User Documentation (Planned)
- [ ] Update user guides
- [ ] Add troubleshooting guides
- [ ] Create FAQ
- [ ] Document new features
## Deployment
### 1. Build Process
- [x] Update build scripts
- [x] Add platform-specific builds
- [ ] Configure CI/CD (planned)
- [ ] Setup automated testing (planned)
### 2. Release Process (Planned)
- [ ] Create release checklist
- [ ] Add version management
- [ ] Setup rollback procedures
- [ ] Configure monitoring
## Monitoring and Analytics (Planned)
### 1. Error Tracking
- [ ] Setup error logging
- [ ] Add performance monitoring
- [ ] Configure alerts
- [ ] Create dashboards
### 2. Usage Analytics
- [ ] Add storage metrics
- [ ] Track migration success
- [ ] Monitor performance
- [ ] Collect user feedback
## Security Audit (Planned)
### 1. Code Review
- [ ] Review encryption
- [ ] Check access controls
- [ ] Verify data handling
- [ ] Audit dependencies
### 2. Penetration Testing
- [ ] Test data access
- [ ] Verify encryption
- [ ] Check authentication
- [ ] Review permissions
## Success Criteria
### 1. Performance
- [x] Query response time < 100ms
- [x] Operation queuing for thread safety
- [x] Proper initialization handling
- [ ] Migration time < 5s per 1000 records (planned)
- [ ] Storage overhead < 10% (planned)
- [ ] Memory usage < 50MB (planned)
### 2. Reliability
- [x] Basic data integrity
- [x] Operation queuing
- [ ] Automatic recovery (planned)
- [ ] Backup verification (planned)
- [ ] Transaction atomicity (planned)
- [ ] Data consistency (planned)
### 3. Security
- [x] Basic data integrity
- [ ] AES-256 encryption (planned)
- [ ] Secure key storage (planned)
- [ ] Access control (planned)
- [ ] Audit logging (planned)
### 4. User Experience
- [x] Basic database operations
- [ ] Smooth migration (planned)
- [ ] Clear error messages (planned)
- [ ] Progress indicators (planned)
- [ ] Recovery options (planned)

View File

@@ -1,210 +0,0 @@
# TimeSafari Docker Compose Configuration
# Author: Matthew Raymer
# Description: Multi-environment Docker Compose setup for TimeSafari
#
# Usage:
# Development: docker-compose up dev
# Staging: docker-compose up staging
# Production: docker-compose up production
# Custom: BUILD_MODE=staging docker-compose up custom
#
# Environment Variables:
# BUILD_MODE: development, staging, or production (default: production)
# NODE_ENV: node environment (default: production)
# VITE_PLATFORM: vite platform (default: web)
# VITE_PWA_ENABLED: enable PWA (default: true)
# VITE_DISABLE_PWA: disable PWA (default: false)
# PORT: port to expose (default: 80 for production, 5173 for dev)
# ENV_FILE: environment file to use (default: .env.production)
#
# See .env files for application-specific configuration
# VITE_APP_SERVER: Application server URL
# VITE_DEFAULT_ENDORSER_API_SERVER: Endorser API server URL
version: '3.8'
# Default values that can be overridden
x-defaults: &defaults
build:
context: .
dockerfile: Dockerfile
args:
BUILD_MODE: ${BUILD_MODE:-production}
NODE_ENV: ${NODE_ENV:-production}
VITE_PLATFORM: ${VITE_PLATFORM:-web}
VITE_PWA_ENABLED: ${VITE_PWA_ENABLED:-true}
VITE_DISABLE_PWA: ${VITE_DISABLE_PWA:-false}
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
services:
# Development service with hot reloading
dev:
<<: *defaults
build:
context: .
dockerfile: Dockerfile
target: development
args:
BUILD_MODE: development
NODE_ENV: development
VITE_PLATFORM: web
VITE_PWA_ENABLED: true
VITE_DISABLE_PWA: false
ports:
- "${DEV_PORT:-5173}:5173"
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
- VITE_PLATFORM=web
- VITE_PWA_ENABLED=true
- VITE_DISABLE_PWA=false
env_file:
- ${DEV_ENV_FILE:-.env.development}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5173"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Staging service for testing
staging:
<<: *defaults
build:
context: .
dockerfile: Dockerfile
target: staging
args:
BUILD_MODE: staging
NODE_ENV: staging
VITE_PLATFORM: web
VITE_PWA_ENABLED: true
VITE_DISABLE_PWA: false
ports:
- "${STAGING_PORT:-8080}:80"
environment:
- NODE_ENV=staging
- VITE_PLATFORM=web
- VITE_PWA_ENABLED=true
- VITE_DISABLE_PWA=false
env_file:
- ${STAGING_ENV_FILE:-.env.staging}
# Production service
production:
<<: *defaults
build:
context: .
dockerfile: Dockerfile
target: production
args:
BUILD_MODE: production
NODE_ENV: production
VITE_PLATFORM: web
VITE_PWA_ENABLED: true
VITE_DISABLE_PWA: false
ports:
- "${PROD_PORT:-80}:80"
environment:
- NODE_ENV=production
- VITE_PLATFORM=web
- VITE_PWA_ENABLED=true
- VITE_DISABLE_PWA=false
env_file:
- ${PROD_ENV_FILE:-.env.production}
# Production service with SSL (requires certificates)
production-ssl:
<<: *defaults
build:
context: .
dockerfile: Dockerfile
target: production
args:
BUILD_MODE: production
NODE_ENV: production
VITE_PLATFORM: web
VITE_PWA_ENABLED: true
VITE_DISABLE_PWA: false
ports:
- "${SSL_PORT:-443}:443"
- "${HTTP_PORT:-80}:80"
environment:
- NODE_ENV=production
- VITE_PLATFORM=web
- VITE_PWA_ENABLED=true
- VITE_DISABLE_PWA=false
env_file:
- ${PROD_ENV_FILE:-.env.production}
volumes:
- ./ssl:/etc/nginx/ssl:ro
- ./docker/nginx-ssl.conf:/etc/nginx/conf.d/default.conf:ro
healthcheck:
test: ["CMD", "curl", "-f", "https://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Custom service - configurable via environment variables
custom:
<<: *defaults
build:
context: .
dockerfile: Dockerfile
target: ${BUILD_TARGET:-production}
args:
BUILD_MODE: ${BUILD_MODE:-production}
NODE_ENV: ${NODE_ENV:-production}
VITE_PLATFORM: ${VITE_PLATFORM:-web}
VITE_PWA_ENABLED: ${VITE_PWA_ENABLED:-true}
VITE_DISABLE_PWA: ${VITE_DISABLE_PWA:-false}
ports:
- "${CUSTOM_PORT:-8080}:${CUSTOM_INTERNAL_PORT:-80}"
environment:
- NODE_ENV=${NODE_ENV:-production}
- VITE_PLATFORM=${VITE_PLATFORM:-web}
- VITE_PWA_ENABLED=${VITE_PWA_ENABLED:-true}
- VITE_DISABLE_PWA=${VITE_DISABLE_PWA:-false}
env_file:
- ${CUSTOM_ENV_FILE:-.env.production}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:${CUSTOM_INTERNAL_PORT:-80}/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Load balancer for production (optional)
nginx-lb:
image: nginx:alpine
ports:
- "${LB_PORT:-80}:80"
- "${LB_SSL_PORT:-443}:443"
volumes:
- ./docker/nginx-lb.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- production
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
default:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16

View File

@@ -1,509 +0,0 @@
# TimeSafari Docker Setup
## Overview
This directory contains Docker configuration files for building and deploying TimeSafari across different environments with full configurability.
## Files
- `Dockerfile` - Multi-stage Docker build for TimeSafari
- `nginx.conf` - Main nginx configuration with security headers
- `default.conf` - Production server configuration
- `staging.conf` - Staging server configuration with relaxed caching
- `docker-compose.yml` - Multi-environment Docker Compose setup
- `.dockerignore` - Optimizes build context
- `run.sh` - Convenient script to run different configurations
## Quick Start
### Using the Run Script (Recommended)
```bash
# Development mode with hot reloading
./docker/run.sh dev
# Staging mode for testing
./docker/run.sh staging
# Production mode
./docker/run.sh production
# Custom mode with environment variables
BUILD_MODE=staging ./docker/run.sh custom
# Show build arguments for a mode
./docker/run.sh dev --build-args
# Custom port and environment file
./docker/run.sh staging --port 9000 --env .env.test
```
### Using Docker Compose
```bash
# Development environment with hot reloading
docker-compose up dev
# Staging environment
docker-compose up staging
# Production environment
docker-compose up production
# Custom environment with variables
BUILD_MODE=staging docker-compose up custom
```
## Build Commands
### Manual Docker Build
```bash
# Build production image (default)
docker build -t timesafari:latest .
# Build staging image
docker build --build-arg BUILD_MODE=staging -t timesafari:staging .
# Build development image
docker build --build-arg BUILD_MODE=development -t timesafari:dev .
# Build with custom arguments
docker build \
--build-arg BUILD_MODE=staging \
--build-arg NODE_ENV=staging \
--build-arg VITE_PWA_ENABLED=true \
-t timesafari:custom .
```
### Run Container
```bash
# Run production container
docker run -d -p 80:80 timesafari:latest
# Run with environment file
docker run -d -p 80:80 --env-file .env.production timesafari:latest
# Run with custom environment variables
docker run -d -p 80:80 \
-e VITE_APP_SERVER=https://myapp.com \
-e VITE_DEFAULT_ENDORSER_API_SERVER=https://api.myapp.com \
timesafari:latest
```
## Configuration Options
### Build Arguments
The Dockerfile supports these build arguments:
| Argument | Default | Description |
|----------|---------|-------------|
| `BUILD_MODE` | `production` | Build mode: development, staging, or production |
| `NODE_ENV` | `production` | Node.js environment |
| `VITE_PLATFORM` | `web` | Vite platform type |
| `VITE_PWA_ENABLED` | `true` | Enable PWA features |
| `VITE_DISABLE_PWA` | `false` | Disable PWA features |
### Environment Variables
Docker Compose supports these environment variables:
| Variable | Default | Description |
|----------|---------|-------------|
| `BUILD_MODE` | `production` | Build mode |
| `NODE_ENV` | `production` | Node environment |
| `VITE_PLATFORM` | `web` | Vite platform |
| `VITE_PWA_ENABLED` | `true` | Enable PWA |
| `VITE_DISABLE_PWA` | `false` | Disable PWA |
| `DEV_PORT` | `5173` | Development port |
| `STAGING_PORT` | `8080` | Staging port |
| `PROD_PORT` | `80` | Production port |
| `DEV_ENV_FILE` | `.env.development` | Development env file |
| `STAGING_ENV_FILE` | `.env.staging` | Staging env file |
| `PROD_ENV_FILE` | `.env.production` | Production env file |
### Environment Files
Create environment files for different deployments:
```bash
# .env.development
VITE_APP_SERVER=https://dev.timesafari.app
VITE_DEFAULT_ENDORSER_API_SERVER=https://dev-api.endorser.ch
VITE_DEFAULT_IMAGE_API_SERVER=https://dev-image-api.timesafari.app
VITE_DEFAULT_PARTNER_API_SERVER=https://dev-partner-api.endorser.ch
VITE_DEFAULT_PUSH_SERVER=https://dev.timesafari.app
VITE_PASSKEYS_ENABLED=true
# .env.staging
VITE_APP_SERVER=https://staging.timesafari.app
VITE_DEFAULT_ENDORSER_API_SERVER=https://staging-api.endorser.ch
VITE_DEFAULT_IMAGE_API_SERVER=https://staging-image-api.timesafari.app
VITE_DEFAULT_PARTNER_API_SERVER=https://staging-partner-api.endorser.ch
VITE_DEFAULT_PUSH_SERVER=https://staging.timesafari.app
VITE_PASSKEYS_ENABLED=true
# .env.production
VITE_APP_SERVER=https://timesafari.app
VITE_DEFAULT_ENDORSER_API_SERVER=https://api.endorser.ch
VITE_DEFAULT_IMAGE_API_SERVER=https://image-api.timesafari.app
VITE_DEFAULT_PARTNER_API_SERVER=https://partner-api.endorser.ch
VITE_DEFAULT_PUSH_SERVER=https://timesafari.app
VITE_PASSKEYS_ENABLED=true
```
## Build Modes
### Development Mode
- **Target**: `development`
- **Features**: Hot reloading, development server
- **Port**: 5173
- **Caching**: Disabled
- **Use Case**: Local development
```bash
./docker/run.sh dev
# or
docker build --target development -t timesafari:dev .
```
### Staging Mode
- **Target**: `staging`
- **Features**: Production build with relaxed caching
- **Port**: 8080 (mapped from 80)
- **Caching**: Short-term (1 hour)
- **Use Case**: Testing and QA
```bash
./docker/run.sh staging
# or
docker build --build-arg BUILD_MODE=staging -t timesafari:staging .
```
### Production Mode
- **Target**: `production`
- **Features**: Optimized production build
- **Port**: 80
- **Caching**: Long-term (1 year for assets)
- **Use Case**: Live deployment
```bash
./docker/run.sh production
# or
docker build -t timesafari:latest .
```
### Custom Mode
- **Target**: Configurable via `BUILD_TARGET`
- **Features**: Fully configurable
- **Port**: Configurable via `CUSTOM_PORT`
- **Use Case**: Special deployments
```bash
BUILD_MODE=staging NODE_ENV=staging ./docker/run.sh custom
```
## Advanced Usage
### Custom Build Configuration
```bash
# Build with specific environment
docker build \
--build-arg BUILD_MODE=staging \
--build-arg NODE_ENV=staging \
--build-arg VITE_PWA_ENABLED=false \
-t timesafari:staging-no-pwa .
# Run with custom configuration
docker run -d -p 9000:80 \
-e VITE_APP_SERVER=https://test.example.com \
timesafari:staging-no-pwa
```
### Docker Compose with Custom Variables
```bash
# Set environment variables
export BUILD_MODE=staging
export NODE_ENV=staging
export STAGING_PORT=9000
export STAGING_ENV_FILE=.env.test
# Run staging with custom config
docker-compose up staging
```
### Multi-Environment Deployment
```bash
# Development
./docker/run.sh dev
# Staging in another terminal
./docker/run.sh staging --port 8081
# Production in another terminal
./docker/run.sh production --port 8082
```
## Security Features
### Built-in Security
- **Non-root user execution**: All containers run as non-root users
- **Security headers**: XSS protection, content type options, frame options
- **Rate limiting**: API request rate limiting
- **File access restrictions**: Hidden files and backup files blocked
- **Minimal attack surface**: Alpine Linux base images
### Security Headers
- `X-Frame-Options: SAMEORIGIN`
- `X-Content-Type-Options: nosniff`
- `X-XSS-Protection: 1; mode=block`
- `Referrer-Policy: strict-origin-when-cross-origin`
- `Content-Security-Policy`: Comprehensive CSP policy
## Performance Optimizations
### Caching Strategy
- **Static assets**: 1 year cache with immutable flag (production)
- **HTML files**: 1 hour cache (production) / no cache (staging)
- **Service worker**: No cache
- **Manifest**: 1 day cache (production) / 1 hour cache (staging)
### Compression
- **Gzip compression**: Enabled for text-based files
- **Compression level**: 6 (balanced)
- **Minimum size**: 1024 bytes
### Nginx Optimizations
- **Sendfile**: Enabled for efficient file serving
- **TCP optimizations**: nopush and nodelay enabled
- **Keepalive**: 65 second timeout
- **Worker processes**: Auto-detected based on CPU cores
## Health Checks
### Built-in Health Checks
All services include health checks that:
- Check every 30 seconds
- Timeout after 10 seconds
- Retry 3 times before marking unhealthy
- Start checking after 40 seconds
### Health Check Endpoints
- **Production/Staging**: `http://localhost/health`
- **Development**: `http://localhost:5173`
## SSL/HTTPS Setup
### SSL Certificates
For SSL deployment, create an `ssl` directory with certificates:
```bash
mkdir ssl
# Copy your certificates to ssl/ directory
cp your-cert.pem ssl/
cp your-key.pem ssl/
```
### SSL Configuration
Use the `production-ssl` service in docker-compose:
```bash
docker-compose up production-ssl
```
## Monitoring and Logging
### Log Locations
- **Access logs**: `/var/log/nginx/access.log`
- **Error logs**: `/var/log/nginx/error.log`
### Log Format
```
$remote_addr - $remote_user [$time_local] "$request"
$status $body_bytes_sent "$http_referer"
"$http_user_agent" "$http_x_forwarded_for"
```
### Log Levels
- **Production**: `warn` level
- **Staging**: `debug` level
- **Development**: Full logging
## Troubleshooting
### Common Issues
#### Build Failures
```bash
# Check build logs
docker build -t timesafari:latest . 2>&1 | tee build.log
# Verify dependencies
docker run --rm timesafari:latest npm list --depth=0
# Check build arguments
./docker/run.sh dev --build-args
```
#### Container Won't Start
```bash
# Check container logs
docker logs <container_id>
# Check health status
docker inspect <container_id> | grep -A 10 "Health"
# Verify port availability
netstat -tulpn | grep :80
```
#### Environment Variables Not Set
```bash
# Check environment in container
docker exec <container_id> env | grep VITE_
# Verify .env file
cat .env.production
# Check build arguments
./docker/run.sh production --build-args
```
#### Performance Issues
```bash
# Check container resources
docker stats <container_id>
# Check nginx configuration
docker exec <container_id> nginx -t
# Monitor access logs
docker exec <container_id> tail -f /var/log/nginx/access.log
```
### Debug Commands
#### Container Debugging
```bash
# Enter running container
docker exec -it <container_id> /bin/sh
# Check nginx status
docker exec <container_id> nginx -t
# Check file permissions
docker exec <container_id> ls -la /usr/share/nginx/html
```
#### Network Debugging
```bash
# Check container network
docker network inspect bridge
# Test connectivity
docker exec <container_id> curl -I http://localhost
# Check DNS resolution
docker exec <container_id> nslookup google.com
```
## Production Deployment
### Recommended Production Setup
1. **Use specific version tags**: `timesafari:1.0.0`
2. **Implement health checks**: Already included
3. **Configure proper logging**: Use external log aggregation
4. **Set up reverse proxy**: Use nginx-lb service
5. **Use Docker secrets**: For sensitive data
### Production Commands
```bash
# Build with specific version
docker build -t timesafari:1.0.0 .
# Run with production settings
docker run -d \
--name timesafari \
-p 80:80 \
--restart unless-stopped \
--env-file .env.production \
timesafari:1.0.0
# Update production deployment
docker stop timesafari
docker rm timesafari
docker build -t timesafari:1.0.1 .
docker run -d --name timesafari -p 80:80 --restart unless-stopped --env-file .env.production timesafari:1.0.1
```
## Development Workflow
### Local Development
```bash
# Start development environment
./docker/run.sh dev
# Make changes to code (hot reloading enabled)
# Access at http://localhost:5173
# Stop development environment
docker-compose down dev
```
### Testing Changes
```bash
# Build and test staging
./docker/run.sh staging
# Test production build locally
./docker/run.sh production
```
### Continuous Integration
```bash
# Build and test in CI
docker build -t timesafari:test .
docker run -d --name timesafari-test -p 8080:80 timesafari:test
# Run tests against container
curl -f http://localhost:8080/health
# Cleanup
docker stop timesafari-test
docker rm timesafari-test
```
## Best Practices
### Security
- Always use non-root users
- Keep base images updated
- Scan images for vulnerabilities
- Use secrets for sensitive data
- Implement proper access controls
### Performance
- Use multi-stage builds
- Optimize layer caching
- Minimize image size
- Use appropriate base images
- Implement proper caching
### Monitoring
- Use health checks
- Monitor resource usage
- Set up log aggregation
- Implement metrics collection
- Use proper error handling
### Maintenance
- Regular security updates
- Monitor for vulnerabilities
- Keep dependencies updated
- Document configuration changes
- Test deployment procedures

View File

@@ -1,110 +0,0 @@
# TimeSafari Default Server Configuration
# Author: Matthew Raymer
# Description: Production server configuration for TimeSafari web application
#
# Features:
# - Vue.js SPA routing support
# - Static file caching optimization
# - Security hardening
# - Performance optimization
# - Proper error handling
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# Handle Vue.js SPA routing
location / {
try_files $uri $uri/ /index.html;
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary "Accept-Encoding";
}
# Cache HTML files for a shorter time
location ~* \.html$ {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}
}
# Handle service worker
location /sw.js {
expires 0;
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
}
# Handle manifest file
location /manifest.json {
expires 1d;
add_header Cache-Control "public";
}
# Handle API requests (if needed)
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Handle health check
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# Handle robots.txt
location /robots.txt {
expires 1d;
add_header Cache-Control "public";
}
# Handle favicon
location /favicon.ico {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Security: Deny access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# Security: Deny access to backup files
location ~ ~$ {
deny all;
access_log off;
log_not_found off;
}
# Error pages
error_page 404 /index.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# Logging
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
}

View File

@@ -1,72 +0,0 @@
# TimeSafari Nginx Configuration
# Author: Matthew Raymer
# Description: Main nginx configuration for TimeSafari web application
#
# Features:
# - Security headers for web application
# - Gzip compression for better performance
# - Proper handling of Vue.js SPA routing
# - Static file caching optimization
# - Security hardening
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging format
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
# Performance optimizations
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 16M;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:; frame-ancestors 'self';" always;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
# Include server configurations
include /etc/nginx/conf.d/*.conf;
}

View File

@@ -1,272 +0,0 @@
#!/bin/bash
# TimeSafari Docker Run Script
# Author: Matthew Raymer
# Description: Convenient script to run TimeSafari in different Docker configurations
#
# Usage:
# ./docker/run.sh dev # Run development mode
# ./docker/run.sh staging # Run staging mode
# ./docker/run.sh production # Run production mode
# ./docker/run.sh custom # Run custom mode with environment variables
#
# Environment Variables:
# BUILD_MODE: development, staging, or production
# NODE_ENV: node environment
# VITE_PLATFORM: vite platform
# VITE_PWA_ENABLED: enable PWA
# VITE_DISABLE_PWA: disable PWA
# PORT: port to expose
# ENV_FILE: environment file to use
set -e
# Colors for output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')] [INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] [SUCCESS]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] [WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR]${NC} $1"
}
# Function to show usage
show_usage() {
echo "TimeSafari Docker Run Script"
echo ""
echo "Usage: $0 <mode> [options]"
echo ""
echo "Modes:"
echo " dev - Development mode with hot reloading"
echo " staging - Staging mode for testing"
echo " production - Production mode"
echo " custom - Custom mode with environment variables"
echo ""
echo "Options:"
echo " --port <port> - Custom port (default: 5173 for dev, 8080 for staging, 80 for production)"
echo " --env <file> - Environment file (default: .env.<mode>)"
echo " --build-args - Show build arguments for the mode"
echo " --help - Show this help message"
echo ""
echo "Examples:"
echo " $0 dev"
echo " $0 staging --port 9000"
echo " $0 production --env .env.prod"
echo " BUILD_MODE=staging $0 custom"
echo ""
echo "Environment Variables:"
echo " BUILD_MODE: development, staging, or production"
echo " NODE_ENV: node environment"
echo " VITE_PLATFORM: vite platform"
echo " VITE_PWA_ENABLED: enable PWA"
echo " VITE_DISABLE_PWA: disable PWA"
echo " PORT: port to expose"
echo " ENV_FILE: environment file to use"
}
# Function to show build arguments for a mode
show_build_args() {
local mode=$1
echo "Build arguments for $mode mode:"
echo ""
case $mode in
dev)
echo " BUILD_MODE: development"
echo " NODE_ENV: development"
echo " VITE_PLATFORM: web"
echo " VITE_PWA_ENABLED: true"
echo " VITE_DISABLE_PWA: false"
echo " Target: development"
echo " Port: 5173"
;;
staging)
echo " BUILD_MODE: staging"
echo " NODE_ENV: staging"
echo " VITE_PLATFORM: web"
echo " VITE_PWA_ENABLED: true"
echo " VITE_DISABLE_PWA: false"
echo " Target: staging"
echo " Port: 80 (mapped to 8080)"
;;
production)
echo " BUILD_MODE: production"
echo " NODE_ENV: production"
echo " VITE_PLATFORM: web"
echo " VITE_PWA_ENABLED: true"
echo " VITE_DISABLE_PWA: false"
echo " Target: production"
echo " Port: 80"
;;
custom)
echo " BUILD_MODE: \${BUILD_MODE:-production}"
echo " NODE_ENV: \${NODE_ENV:-production}"
echo " VITE_PLATFORM: \${VITE_PLATFORM:-web}"
echo " VITE_PWA_ENABLED: \${VITE_PWA_ENABLED:-true}"
echo " VITE_DISABLE_PWA: \${VITE_DISABLE_PWA:-false}"
echo " Target: \${BUILD_TARGET:-production}"
echo " Port: \${CUSTOM_PORT:-8080}:\${CUSTOM_INTERNAL_PORT:-80}"
;;
*)
log_error "Unknown mode: $mode"
exit 1
;;
esac
}
# Function to check if Docker is running
check_docker() {
if ! docker info > /dev/null 2>&1; then
log_error "Docker is not running. Please start Docker and try again."
exit 1
fi
}
# Function to check if docker-compose is available
check_docker_compose() {
if ! command -v docker-compose > /dev/null 2>&1; then
log_error "docker-compose is not installed. Please install docker-compose and try again."
exit 1
fi
}
# Function to check if required files exist
check_files() {
local mode=$1
local env_file=$2
if [ ! -f "Dockerfile" ]; then
log_error "Dockerfile not found. Please run this script from the project root."
exit 1
fi
if [ ! -f "docker-compose.yml" ]; then
log_error "docker-compose.yml not found. Please run this script from the project root."
exit 1
fi
if [ -n "$env_file" ] && [ ! -f "$env_file" ]; then
log_warn "Environment file $env_file not found. Using defaults."
fi
}
# Function to run the container
run_container() {
local mode=$1
local port=$2
local env_file=$3
log_info "Starting TimeSafari in $mode mode..."
# Set environment variables based on mode
case $mode in
dev)
export DEV_PORT=${port:-5173}
if [ -n "$env_file" ]; then
export DEV_ENV_FILE="$env_file"
fi
docker-compose up dev
;;
staging)
export STAGING_PORT=${port:-8080}
if [ -n "$env_file" ]; then
export STAGING_ENV_FILE="$env_file"
fi
docker-compose up staging
;;
production)
export PROD_PORT=${port:-80}
if [ -n "$env_file" ]; then
export PROD_ENV_FILE="$env_file"
fi
docker-compose up production
;;
custom)
export CUSTOM_PORT=${port:-8080}
if [ -n "$env_file" ]; then
export CUSTOM_ENV_FILE="$env_file"
fi
docker-compose up custom
;;
*)
log_error "Unknown mode: $mode"
exit 1
;;
esac
}
# Main script
main() {
local mode=""
local port=""
local env_file=""
local show_args=false
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
dev|staging|production|custom)
mode="$1"
shift
;;
--port)
port="$2"
shift 2
;;
--env)
env_file="$2"
shift 2
;;
--build-args)
show_args=true
shift
;;
--help|-h)
show_usage
exit 0
;;
*)
log_error "Unknown option: $1"
show_usage
exit 1
;;
esac
done
# Check if mode is provided
if [ -z "$mode" ]; then
log_error "No mode specified."
show_usage
exit 1
fi
# Show build arguments if requested
if [ "$show_args" = true ]; then
show_build_args "$mode"
exit 0
fi
# Check prerequisites
check_docker
check_docker_compose
check_files "$mode" "$env_file"
# Run the container
run_container "$mode" "$port" "$env_file"
}
# Run main function with all arguments
main "$@"

View File

@@ -1,110 +0,0 @@
# TimeSafari Staging Server Configuration
# Author: Matthew Raymer
# Description: Staging server configuration for TimeSafari web application
#
# Features:
# - Relaxed caching for testing
# - Debug-friendly settings
# - Same security as production
# - Development-friendly error handling
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Security headers (same as production)
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# Handle Vue.js SPA routing
location / {
try_files $uri $uri/ /index.html;
# Relaxed caching for staging
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1h;
add_header Cache-Control "public, must-revalidate";
add_header Vary "Accept-Encoding";
}
# No caching for HTML files in staging
location ~* \.html$ {
expires 0;
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
}
}
# Handle service worker (no caching)
location /sw.js {
expires 0;
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
}
# Handle manifest file (short cache)
location /manifest.json {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}
# Handle API requests (if needed)
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Handle health check
location /health {
access_log off;
return 200 "healthy-staging\n";
add_header Content-Type text/plain;
}
# Handle robots.txt (no caching in staging)
location /robots.txt {
expires 0;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# Handle favicon (short cache)
location /favicon.ico {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}
# Security: Deny access to hidden files
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# Security: Deny access to backup files
location ~ ~$ {
deny all;
access_log off;
log_not_found off;
}
# Error pages (more verbose for staging)
error_page 404 /index.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# Enhanced logging for staging
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log debug;
}

View File

@@ -1,115 +0,0 @@
# AbsurdSQL Enhanced Logging - Security Audit Checklist
**Date:** July 1, 2025
**Author:** Matthew Raymer
**Changes:** Enhanced AbsurdSQL logging with comprehensive failure tracking
## Overview
This security audit covers the enhanced logging implementation for AbsurdSQL database service, including diagnostic capabilities and health monitoring features.
## Security Audit Checklist
### 1. Data Exposure and Privacy
- [x] **Sensitive Data Logging**: Verified that SQL parameters are logged but PII data is not exposed in plain text
- [x] **SQL Injection Prevention**: Confirmed parameterized queries are used throughout, no string concatenation
- [x] **Error Message Sanitization**: Error messages don't expose internal system details to external users
- [x] **Diagnostic Data Scope**: Diagnostic information includes only operational metrics, not user data
- [x] **Log Level Appropriateness**: Debug logs contain operational details, info logs contain high-level status
### 2. Authentication and Authorization
- [x] **Access Control**: Diagnostic methods are internal to the application, not exposed via external APIs
- [x] **Method Visibility**: All diagnostic methods are properly scoped and not publicly accessible
- [x] **Component Security**: Test component is development-only and should not be included in production builds
- [x] **Service Layer Protection**: Database service maintains singleton pattern preventing unauthorized instantiation
### 3. Input Validation and Sanitization
- [x] **Parameter Validation**: SQL parameters are validated through existing platform service layer
- [x] **Query Sanitization**: All queries use parameterized statements, preventing SQL injection
- [x] **Log Message Sanitization**: Log messages are properly escaped and truncated to prevent log injection
- [x] **Diagnostic Output Sanitization**: Diagnostic output is structured JSON, preventing injection attacks
### 4. Resource Management and DoS Prevention
- [x] **Queue Size Monitoring**: Warning logs when operation queue exceeds 50 items
- [x] **Memory Management**: Diagnostic data is bounded and doesn't accumulate indefinitely
- [x] **Performance Impact**: Logging operations are asynchronous and non-blocking
- [x] **Log Rotation**: Relies on external log management system for rotation and cleanup
- [x] **Resource Cleanup**: Proper cleanup of diagnostic resources and temporary data
### 5. Information Disclosure
- [x] **Stack Trace Handling**: Full stack traces only logged at debug level, not exposed to users
- [x] **System Information**: Minimal system information logged (platform, browser type only)
- [x] **Database Schema Protection**: No database schema information exposed in logs
- [x] **Operational Metrics**: Only performance metrics exposed, not sensitive operational data
### 6. Error Handling and Recovery
- [x] **Graceful Degradation**: Diagnostic features fail gracefully without affecting core functionality
- [x] **Error Isolation**: Logging failures don't cascade to database operations
- [x] **Recovery Mechanisms**: Initialization failures are properly handled with retry logic
- [x] **State Consistency**: Database state remains consistent even if logging fails
### 7. Cross-Platform Security
- [x] **Web Platform**: Browser-based logging doesn't expose server-side information
- [x] **Mobile Platform**: Capacitor implementation properly sandboxes diagnostic data
- [x] **Platform Isolation**: Platform-specific diagnostic data is properly isolated
- [x] **Interface Consistency**: All platforms implement the same security model
### 8. Compliance and Audit Trail
- [x] **Audit Logging**: Comprehensive audit trail for database operations and health checks
- [x] **Timestamp Accuracy**: All logs include accurate ISO timestamps
- [x] **Data Retention**: Logs are managed by external system for compliance requirements
- [x] **Traceability**: Operation IDs enable tracing of database operations
## Security Recommendations
### High Priority
1. **Production Builds**: Ensure `DiagnosticsTestComponent` is excluded from production builds
2. **Log Level Configuration**: Implement runtime log level configuration for production
3. **Rate Limiting**: Consider implementing rate limiting for diagnostic operations
### Medium Priority
1. **Log Encryption**: Consider encrypting sensitive diagnostic data at rest
2. **Access Logging**: Add logging for diagnostic method access patterns
3. **Automated Monitoring**: Implement automated alerting for diagnostic anomalies
### Low Priority
1. **Log Aggregation**: Implement centralized log aggregation for better analysis
2. **Metrics Dashboard**: Create operational dashboard for diagnostic metrics
3. **Performance Profiling**: Add performance profiling for diagnostic operations
## Compliance Notes
- **GDPR**: No personal data is logged in diagnostic information
- **HIPAA**: Medical data is not exposed through diagnostic channels
- **SOC 2**: Audit trails are maintained for all database operations
- **ISO 27001**: Information security controls are implemented for logging
## Testing and Validation
### Security Tests Required
- [ ] Penetration testing of diagnostic endpoints
- [ ] Log injection attack testing
- [ ] Resource exhaustion testing
- [ ] Cross-site scripting (XSS) testing of diagnostic output
- [ ] Authentication bypass testing
### Monitoring and Alerting
- [ ] Set up alerts for unusual diagnostic patterns
- [ ] Monitor for potential information disclosure
- [ ] Track diagnostic performance impact
- [ ] Monitor queue growth patterns
## Sign-off
**Security Review Completed:** July 1, 2025
**Reviewer:** Matthew Raymer
**Status:** ✅ Approved with recommendations
**Next Review:** October 1, 2025

View File

@@ -1,209 +0,0 @@
# Compact Database API - Before vs After Comparison
## The Problem: Verbose Database Operations
The current database operations require significant boilerplate code, making simple operations unnecessarily complex.
## Before: Verbose & Repetitive ❌
### Loading Data
```typescript
// 6 lines for a simple query!
@Component
export default class ContactsView extends Vue {
async loadContacts() {
const platformService = PlatformServiceFactory.getInstance();
const result = await platformService.dbQuery("SELECT * FROM contacts WHERE visible = ?", [1]);
const contacts = databaseUtil.mapQueryResultToValues(result) as Contact[];
await databaseUtil.logToDb(`Loaded ${contacts.length} contacts`);
this.contacts = contacts;
}
}
```
### Saving Data
```typescript
// 8+ lines for a simple insert!
async saveContact(contact: Contact) {
const platformService = PlatformServiceFactory.getInstance();
const { sql, params } = databaseUtil.generateInsertStatement(contact, "contacts");
const result = await platformService.dbExec(sql, params);
await databaseUtil.logToDb(`Contact saved with ID: ${result.lastId}`);
if (result.changes !== 1) {
throw new Error("Failed to save contact");
}
return result;
}
```
### Settings Management
```typescript
// 4+ lines for settings
async updateAppSettings(newSettings: Partial<Settings>) {
const success = await databaseUtil.updateDefaultSettings(newSettings as Settings);
await databaseUtil.logToDb(success ? "Settings saved" : "Settings save failed", success ? "info" : "error");
return success;
}
```
## After: Compact & Clean ✅
### Loading Data
```typescript
// 2 lines - 70% reduction!
@Component
export default class ContactsView extends Vue {
private db = useCompactDatabase();
async loadContacts() {
const contacts = await this.db.query<Contact>("SELECT * FROM contacts WHERE visible = ?", [1]);
await this.db.log(`Loaded ${contacts.length} contacts`);
this.contacts = contacts;
}
}
```
### Saving Data
```typescript
// 2 lines - 75% reduction!
async saveContact(contact: Contact) {
const result = await this.db.insert("contacts", contact);
await this.db.log(`Contact saved with ID: ${result.lastId}`);
return result;
}
```
### Settings Management
```typescript
// 1 line - 75% reduction!
async updateAppSettings(newSettings: Partial<Settings>) {
return await this.db.saveSettings(newSettings);
}
```
## Advanced Examples
### Multiple Usage Patterns
#### 1. Vue-Facing-Decorator Class Components
```typescript
@Component
export default class MyComponent extends Vue {
private db = useCompactDatabase(); // Composable in class
async mounted() {
// Query with type safety
const users = await this.db.query<User>("SELECT * FROM users WHERE active = ?", [1]);
// Get single record
const setting = await this.db.queryOne<Setting>("SELECT * FROM settings WHERE key = ?", ["theme"]);
// CRUD operations
await this.db.insert("logs", { message: "Component mounted", date: new Date().toISOString() });
await this.db.update("users", { lastActive: Date.now() }, "id = ?", [this.userId]);
await this.db.delete("temp_data", "created < ?", [Date.now() - 86400000]);
}
}
```
#### 2. Composition API Setup
```typescript
export default {
setup() {
const db = useCompactDatabase();
const loadData = async () => {
const items = await db.query("SELECT * FROM items");
await db.log("Data loaded");
return items;
};
return { loadData };
}
}
```
#### 3. Direct Import (Non-Composable)
```typescript
import { db } from "@/composables/useCompactDatabase";
// Use anywhere without setup
export async function backgroundTask() {
const data = await db.query("SELECT * FROM background_jobs");
await db.log(`Processing ${data.length} jobs`);
}
```
## Feature Comparison
| Operation | Before (Lines) | After (Lines) | Reduction |
|-----------|----------------|---------------|-----------|
| Simple Query | 4 lines | 1 line | **75%** |
| Insert Record | 4 lines | 1 line | **75%** |
| Update Record | 5 lines | 1 line | **80%** |
| Delete Record | 3 lines | 1 line | **67%** |
| Get Settings | 3 lines | 1 line | **67%** |
| Save Settings | 4 lines | 1 line | **75%** |
| Log Message | 1 line | 1 line | **0%** (already compact) |
## Benefits
### 🎯 Massive Code Reduction
- **70-80% less boilerplate** for common operations
- **Cleaner, more readable code**
- **Faster development** with less typing
### 🔧 Developer Experience
- **Auto-completion** for all database operations
- **Type safety** with generic query methods
- **Consistent API** across all database operations
- **Built-in logging** for debugging
### 🛡️ Safety & Reliability
- **Same security** as existing functions (wraps them)
- **Parameterized queries** prevent SQL injection
- **Error handling** built into the composable
- **Type checking** prevents runtime errors
### 🔄 Flexibility
- **Works with vue-facing-decorator** (your current pattern)
- **Works with Composition API** (future-proof)
- **Works with direct imports** (utility functions)
- **Progressive adoption** - use alongside existing code
## Migration Path
### Phase 1: New Code
```typescript
// Start using in new components immediately
const db = useCompactDatabase();
const data = await db.query("SELECT * FROM table");
```
### Phase 2: Gradual Replacement
```typescript
// Replace verbose patterns as you encounter them
// Old:
const platformService = PlatformServiceFactory.getInstance();
const result = await platformService.dbQuery(sql, params);
const mapped = databaseUtil.mapQueryResultToValues(result);
// New:
const mapped = await db.query(sql, params);
```
### Phase 3: Full Adoption
```typescript
// Eventually all database operations use the compact API
```
## Performance Impact
- **Zero performance overhead** - same underlying functions
- **Slight memory improvement** - fewer service instantiations
- **Better caching** - singleton pattern for platform service
- **Reduced bundle size** - less repeated boilerplate code
---
**The compact database composable transforms verbose, error-prone database operations into clean, type-safe one-liners while maintaining all existing security and functionality.**

View File

@@ -1,206 +0,0 @@
# HomeView Migration Results - Compact Database Success ✅
## Overview (Tue Jul 1 08:49:04 AM UTC 2025)
Successfully migrated **HomeView.vue** from verbose database patterns to the compact database API. This migration demonstrates the dramatic code reduction and improved maintainability achieved with the new approach.
## Migration Statistics
### 📊 **Code Reduction Summary**
- **5 methods migrated** with database operations
- **Lines of code reduced**: 12 lines → 5 lines (**58% reduction**)
- **Import statements reduced**: 2 imports → 1 import
- **Complexity reduced**: Eliminated boilerplate in all database operations
### 🎯 **Specific Method Improvements**
#### 1. `loadContacts()` - Most Dramatic Improvement
```typescript
// BEFORE (3 lines)
const platformService = PlatformServiceFactory.getInstance();
const dbContacts = await platformService.dbQuery("SELECT * FROM contacts");
this.allContacts = databaseUtil.mapQueryResultToValues(dbContacts) as unknown as Contact[];
// AFTER (1 line) ✅
this.allContacts = await this.db.query<Contact>("SELECT * FROM contacts");
```
**Result**: 67% reduction, **cleaner types**, **better readability**
#### 2. Settings Methods - Consistent Simplification
```typescript
// BEFORE (1 line each)
const settings = await databaseUtil.retrieveSettingsForActiveAccount();
// AFTER (1 line each) ✅
const settings = await this.db.getSettings();
```
**Result**: **Shorter**, **more semantic**, **consistent API**
#### 3. Import Cleanup
```typescript
// BEFORE (2 imports)
import * as databaseUtil from "../db/databaseUtil";
import { PlatformServiceFactory } from "@/services/PlatformServiceFactory";
// AFTER (1 import) ✅
import { useCompactDatabase } from "@/composables/useCompactDatabase";
```
**Result**: **Cleaner imports**, **single dependency**, **better organization**
## Methods Successfully Migrated
### ✅ **5 Methods Converted**
1. **`loadSettings()`**
- `databaseUtil.retrieveSettingsForActiveAccount()``this.db.getSettings()`
2. **`loadContacts()`**
- 3-line query pattern → 1-line typed query
- Automatic result mapping
- Type safety with `<Contact>`
3. **`checkRegistrationStatus()`**
- Settings retrieval simplified
- Maintained complex update logic (not yet migrated)
4. **`checkOnboarding()`**
- Settings retrieval simplified
5. **`reloadFeedOnChange()`**
- Settings retrieval simplified
## Benefits Demonstrated
### 🚀 **Developer Experience**
- **Less typing**: Fewer lines of boilerplate code
- **Better IntelliSense**: Typed methods with clear signatures
- **Consistent API**: Same patterns across all operations
- **Reduced errors**: Fewer manual mapping steps
### 🔧 **Maintainability**
- **Single point of change**: Database logic centralized
- **Clear separation**: Business logic vs database operations
- **Better testing**: Easier to mock and test
- **Reduced complexity**: Fewer moving parts
### 📈 **Performance**
- **Singleton pattern**: Reused database instance
- **Optimized queries**: Direct result mapping
- **Reduced memory**: Fewer intermediate objects
- **Better caching**: Centralized database management
## Code Quality Improvements
### ✅ **Linting & Formatting**
- **Zero lint errors**: All code passes ESLint
- **Consistent formatting**: Auto-formatted with Prettier
- **TypeScript compliance**: Full type safety maintained
- **Import optimization**: Unused imports removed
### ✅ **Vue-Facing-Decorator Compatibility**
- **Class-based syntax**: Works perfectly with decorator pattern
- **Private instance**: `private db = useCompactDatabase()`
- **Method integration**: Seamless integration with existing methods
- **Component lifecycle**: No conflicts with Vue lifecycle
## Migration Patterns Identified
### 🔄 **Reusable Patterns**
#### Pattern 1: Simple Query
```typescript
// BEFORE
const platformService = PlatformServiceFactory.getInstance();
const result = await platformService.dbQuery(sql, params);
const data = databaseUtil.mapQueryResultToValues(result) as Type[];
// AFTER
const data = await this.db.query<Type>(sql, params);
```
#### Pattern 2: Settings Retrieval
```typescript
// BEFORE
const settings = await databaseUtil.retrieveSettingsForActiveAccount();
// AFTER
const settings = await this.db.getSettings();
```
#### Pattern 3: Settings Update (Future)
```typescript
// FUTURE MIGRATION TARGET
const settings = await this.db.getSettings();
await databaseUtil.updateDidSpecificSettings(did, changes);
// COULD BECOME
await this.db.updateSettings(did, changes);
```
## Remaining Migration Opportunities
### 🎯 **Next Steps**
1. **Settings updates**: Migrate `updateDidSpecificSettings()` calls
2. **Other views**: Apply same patterns to other Vue components
3. **Service methods**: Migrate services that use database operations
4. **CRUD operations**: Use compact database CRUD helpers
### 📋 **Migration Checklist for Other Components**
- [ ] Add `useCompactDatabase` import
- [ ] Create `private db = useCompactDatabase()` instance
- [ ] Replace query patterns with `db.query<Type>()`
- [ ] Replace settings patterns with `db.getSettings()`
- [ ] Remove unused imports
- [ ] Run lint-fix
## Testing Recommendations
### 🧪 **Validation Steps**
1. **Functional testing**: Verify all HomeView features work
2. **Database operations**: Confirm queries return expected data
3. **Settings management**: Test settings load/save operations
4. **Error handling**: Ensure error scenarios are handled
5. **Performance**: Monitor query performance
### 🔍 **What to Test**
- Contact loading and display
- Settings persistence across sessions
- Registration status checks
- Onboarding flow
- Feed filtering functionality
## Security Considerations
### 🔒 **Security Maintained**
- **Same SQL queries**: No query logic changed
- **Same permissions**: No privilege escalation
- **Same validation**: Input validation preserved
- **Same error handling**: Error patterns maintained
### ✅ **Security Checklist**
- [x] No SQL injection vectors introduced
- [x] Same data access patterns maintained
- [x] Error messages don't leak sensitive data
- [x] Database permissions unchanged
- [x] Input validation preserved
## Conclusion
The HomeView migration to compact database is a **complete success**. It demonstrates:
- **Significant code reduction** (58% fewer lines)
- **Improved readability** and maintainability
- **Better developer experience** with typed APIs
- **Zero regression** in functionality
- **Clear migration patterns** for other components
This migration serves as a **proof of concept** and **template** for migrating the entire codebase to the compact database approach.
## Next Migration Targets
1. **ContactsView** - Likely heavy database usage
2. **ProjectsView** - Complex query patterns
3. **ServicesView** - Business logic integration
4. **ClaimView** - Data persistence operations
The compact database approach is **production-ready** and **ready for full codebase adoption**.

File diff suppressed because it is too large Load Diff

8
electron/.gitignore vendored
View File

@@ -1,8 +0,0 @@
# NPM renames .gitignore to .npmignore
# In order to prevent that, we remove the initial "."
# And the CLI then renames it
app
node_modules
build
dist
logs

View File

@@ -1,251 +0,0 @@
# Building TimeSafari Electron App
This guide explains how to build distributable packages for the TimeSafari Electron desktop application.
## Quick Start
### From Project Root
```bash
# Build all Linux packages (AppImage, deb)
npm run electron:build
# Build specific package types
npm run electron:build:appimage # AppImage only
npm run electron:build:deb # Debian package only
```
### From Electron Directory
```bash
cd electron
# Build all packages
./build-packages.sh
# Build specific types
./build-packages.sh appimage
./build-packages.sh deb
./build-packages.sh pack # Unpacked directory (for testing)
```
## Package Types
### 1. AppImage (Recommended for Linux)
- **File**: `TimeSafari-1.0.0.AppImage`
- **Size**: ~145MB
- **Usage**: Download and run directly, no installation required
- **Distribution**: Upload to GitHub releases or website
```bash
# Make executable and run
chmod +x TimeSafari-1.0.0.AppImage
./TimeSafari-1.0.0.AppImage
```
### 2. Debian Package (.deb)
- **File**: `TimeSafari_1.0.0_amd64.deb`
- **Size**: ~96MB
- **Usage**: Install via package manager
- **Distribution**: Upload to repositories or direct download
```bash
# Install
sudo dpkg -i TimeSafari_1.0.0_amd64.deb
# Run
timesafari
```
### 3. RPM Package (.rpm)
- **File**: `TimeSafari-1.0.0.x86_64.rpm`
- **Requirements**: `rpmbuild` must be installed
- **Usage**: Install via package manager
```bash
# Install rpmbuild (Arch Linux)
sudo pacman -S rpm-tools
# Build RPM
./build-packages.sh rpm
# Install (on RPM-based systems)
sudo rpm -i TimeSafari-1.0.0.x86_64.rpm
```
## Build Requirements
### System Dependencies
- Node.js 18+
- npm or yarn
- Python 3 (for native module compilation)
- Build tools (gcc, make)
### Optional Dependencies
- `rpmbuild` - for RPM packages
- `fpm` - automatically downloaded by electron-builder
### Node Dependencies
All required dependencies are in `package.json`:
- `electron-builder` - Main build tool
- `better-sqlite3-multiple-ciphers` - SQLite with encryption
- Native module compilation tools
## Build Process
### 1. Preparation
```bash
# Install dependencies
npm install
# Build TypeScript
npm run build
```
### 2. Package Creation
The build process:
1. Compiles TypeScript to JavaScript
2. Rebuilds native modules for Electron
3. Packages the app with electron-builder
4. Creates platform-specific installers
### 3. Output Location
All built packages are saved to `electron/dist/`:
```
dist/
├── TimeSafari-1.0.0.AppImage # Portable AppImage
├── TimeSafari_1.0.0_amd64.deb # Debian package
├── TimeSafari-1.0.0.x86_64.rpm # RPM package (if built)
└── linux-unpacked/ # Unpacked directory
```
## Configuration
### App Metadata
App information is configured in `electron/package.json`:
```json
{
"name": "TimeSafari",
"version": "1.0.0",
"description": "Time Safari - Community building through gifts, gratitude, and collaborative projects",
"homepage": "https://timesafari.app",
"author": {
"name": "Matthew Raymer",
"email": "matthew@timesafari.app"
}
}
```
### Build Configuration
Build settings are in `electron/electron-builder.config.json`:
- Package formats and architectures
- Icons and assets
- Platform-specific settings
- Signing and publishing options
## Troubleshooting
### Common Issues
#### 1. Native Module Compilation Errors
```bash
# Clear cache and rebuild
npm run build
```
#### 2. Missing Dependencies
```bash
# Install system dependencies (Arch Linux)
sudo pacman -S base-devel python
# Install Node dependencies
npm install
```
#### 3. RPM Build Fails
```bash
# Install rpmbuild
sudo pacman -S rpm-tools
# Try building again
./build-packages.sh rpm
```
#### 4. Large Package Size
The packages are large (~100-150MB) because they include:
- Complete Electron runtime
- Node.js runtime
- SQLite native modules
- Application assets
This is normal for Electron applications.
### Debug Mode
For detailed build information:
```bash
DEBUG=electron-builder npx electron-builder build
```
## Distribution
### GitHub Releases
1. Create a new release on GitHub
2. Upload the built packages as release assets
3. Users can download and install directly
### Package Repositories
- **Debian/Ubuntu**: Upload `.deb` to repository
- **Fedora/CentOS**: Upload `.rpm` to repository
- **Arch Linux**: Create PKGBUILD for AUR
### Direct Download
Host the packages on your website for direct download.
## Cross-Platform Building
### Current Support
- **Linux**: Full support (AppImage, deb, rpm)
- **Windows**: Configured but requires Windows build environment
- **macOS**: Configured but requires macOS build environment
### Building for Other Platforms
To build for Windows or macOS, you need:
- The target platform's build environment
- Platform-specific signing certificates
- Updated build configuration
## Security Considerations
### Code Signing
For production releases, consider code signing:
- **Linux**: Not required but recommended
- **Windows**: Required for Windows SmartScreen
- **macOS**: Required for Gatekeeper
### Package Integrity
- Verify package checksums
- Use HTTPS for distribution
- Consider GPG signatures for packages
## Performance Tips
### Build Optimization
- Use `--dir` flag for faster development builds
- Cache node_modules between builds
- Use CI/CD for automated builds
### Package Size Reduction
- Remove unnecessary dependencies
- Use electron-builder's file filtering
- Consider using electron-updater for delta updates
## Support
For build issues:
1. Check the console output for specific errors
2. Verify all dependencies are installed
3. Try cleaning and rebuilding
4. Check electron-builder documentation
5. Open an issue with build logs
---
**Happy Building! 🚀**

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,56 +0,0 @@
#!/bin/bash
# TimeSafari Electron Build Script
# Usage: ./build-packages.sh [pack|appimage|deb|rpm|all]
set -e
echo "🚀 TimeSafari Electron Build Script"
echo "=================================="
# Build TypeScript and rebuild native modules
echo "📦 Building TypeScript and native modules..."
npm run build
BUILD_TYPE="${1:-all}"
case "$BUILD_TYPE" in
"pack")
echo "📦 Creating unpacked build..."
npx electron-builder build --dir -c ./electron-builder.config.json
;;
"appimage")
echo "📦 Creating AppImage..."
npx electron-builder build --linux AppImage -c ./electron-builder.config.json
;;
"deb")
echo "📦 Creating Debian package..."
npx electron-builder build --linux deb -c ./electron-builder.config.json
;;
"rpm")
echo "📦 Creating RPM package..."
if ! command -v rpmbuild &> /dev/null; then
echo "⚠️ rpmbuild not found. Install with: sudo pacman -S rpm-tools"
exit 1
fi
npx electron-builder build --linux rpm -c ./electron-builder.config.json
;;
"all")
echo "📦 Creating all Linux packages..."
npx electron-builder build --linux -c ./electron-builder.config.json
;;
*)
echo "❌ Unknown build type: $BUILD_TYPE"
echo "Usage: $0 [pack|appimage|deb|rpm|all]"
exit 1
;;
esac
echo ""
echo "✅ Build completed successfully!"
echo "📁 Output files in: ./dist/"
echo ""
echo "📦 Available packages:"
ls -la dist/ | grep -E '\.(AppImage|deb|rpm)$' || echo " No packages found"
echo ""
echo "🎉 Ready to distribute!"

View File

@@ -1,98 +0,0 @@
{
"appId": "app.timesafari",
"appName": "TimeSafari",
"webDir": "dist",
"server": {
"cleartext": true
},
"plugins": {
"App": {
"appUrlOpen": {
"handlers": [
{
"url": "timesafari://*",
"autoVerify": true
}
]
}
},
"CapacitorSQLite": {
"iosDatabaseLocation": "Library/CapacitorDatabase",
"iosIsEncryption": false,
"iosBiometric": {
"biometricAuth": false,
"biometricTitle": "Biometric login for TimeSafari"
},
"androidIsEncryption": false,
"androidBiometric": {
"biometricAuth": false,
"biometricTitle": "Biometric login for TimeSafari"
},
"electronIsEncryption": false
}
},
"ios": {
"contentInset": "never",
"allowsLinkPreview": true,
"scrollEnabled": true,
"limitsNavigationsToAppBoundDomains": true,
"backgroundColor": "#ffffff",
"allowNavigation": [
"*.timesafari.app",
"*.jsdelivr.net",
"api.endorser.ch"
]
},
"android": {
"allowMixedContent": false,
"captureInput": true,
"webContentsDebuggingEnabled": false,
"allowNavigation": [
"*.timesafari.app",
"*.jsdelivr.net",
"api.endorser.ch"
]
},
"electron": {
"deepLinking": {
"schemes": ["timesafari"]
},
"buildOptions": {
"appId": "app.timesafari",
"productName": "TimeSafari",
"directories": {
"output": "dist-electron-packages"
},
"files": [
"dist/**/*",
"electron/**/*"
],
"mac": {
"category": "public.app-category.productivity",
"target": [
{
"target": "dmg",
"arch": ["x64", "arm64"]
}
]
},
"win": {
"target": [
{
"target": "nsis",
"arch": ["x64"]
}
]
},
"linux": {
"target": [
{
"target": "AppImage",
"arch": ["x64"]
}
],
"category": "Utility"
}
}
}
}

View File

@@ -1,64 +0,0 @@
{
"appId": "app.timesafari.desktop",
"productName": "TimeSafari",
"directories": {
"buildResources": "resources",
"output": "dist"
},
"files": [
"assets/**/*",
"build/**/*",
"capacitor.config.*",
"app/**/*"
],
"publish": {
"provider": "github"
},
"linux": {
"target": [
{
"target": "AppImage",
"arch": ["x64"]
},
{
"target": "deb",
"arch": ["x64"]
},
{
"target": "rpm",
"arch": ["x64"]
}
],
"icon": "assets/appIcon.png",
"category": "Office",
"description": "Time Safari - Community building through gifts, gratitude, and collaborative projects",
"maintainer": "Matthew Raymer <matthew@timesafari.app>",
"vendor": "TimeSafari"
},
"nsis": {
"allowElevation": true,
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true
},
"win": {
"target": [
{
"target": "nsis",
"arch": ["x64"]
}
],
"icon": "assets/appIcon.ico"
},
"mac": {
"category": "public.app-category.productivity",
"target": [
{
"target": "dmg",
"arch": ["x64", "arm64"]
}
],
"icon": "assets/appIcon.png"
}
}

View File

@@ -1,75 +0,0 @@
/* eslint-disable no-undef */
/* eslint-disable @typescript-eslint/no-var-requires */
const cp = require('child_process');
const chokidar = require('chokidar');
const electron = require('electron');
let child = null;
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
const reloadWatcher = {
debouncer: null,
ready: false,
watcher: null,
restarting: false,
};
///*
function runBuild() {
return new Promise((resolve, _reject) => {
let tempChild = cp.spawn(npmCmd, ['run', 'build']);
tempChild.once('exit', () => {
resolve();
});
tempChild.stdout.pipe(process.stdout);
});
}
//*/
async function spawnElectron() {
if (child !== null) {
child.stdin.pause();
child.kill();
child = null;
await runBuild();
}
child = cp.spawn(electron, ['--inspect=5858', './']);
child.on('exit', () => {
if (!reloadWatcher.restarting) {
process.exit(0);
}
});
child.stdout.pipe(process.stdout);
}
function setupReloadWatcher() {
reloadWatcher.watcher = chokidar
.watch('./src/**/*', {
ignored: /[/\\]\./,
persistent: true,
})
.on('ready', () => {
reloadWatcher.ready = true;
})
.on('all', (_event, _path) => {
if (reloadWatcher.ready) {
clearTimeout(reloadWatcher.debouncer);
reloadWatcher.debouncer = setTimeout(async () => {
console.log('Restarting');
reloadWatcher.restarting = true;
await spawnElectron();
reloadWatcher.restarting = false;
reloadWatcher.ready = false;
clearTimeout(reloadWatcher.debouncer);
reloadWatcher.debouncer = null;
reloadWatcher.watcher = null;
setupReloadWatcher();
}, 500);
}
});
}
(async () => {
await runBuild();
await spawnElectron();
setupReloadWatcher();
})();

File diff suppressed because it is too large Load Diff

View File

@@ -1,52 +0,0 @@
{
"name": "TimeSafari",
"version": "1.0.0",
"description": "Time Safari - Community building through gifts, gratitude, and collaborative projects",
"homepage": "https://timesafari.app",
"author": {
"name": "Matthew Raymer",
"email": "matthew@timesafari.app"
},
"repository": {
"type": "git",
"url": "https://github.com/trentlarson/crowd-master"
},
"license": "MIT",
"main": "build/src/index.js",
"scripts": {
"build": "tsc && electron-rebuild",
"electron:start-live": "node ./live-runner.js",
"electron:start": "npm run build && electron --inspect=5858 ./",
"electron:pack": "npm run build && electron-builder build --dir -c ./electron-builder.config.json",
"electron:make": "npm run build && electron-builder build -c ./electron-builder.config.json -p always"
},
"dependencies": {
"@capacitor-community/electron": "^5.0.0",
"@capacitor-community/sqlite": "^6.0.2",
"better-sqlite3-multiple-ciphers": "^12.1.1",
"chokidar": "~3.5.3",
"crypto": "^1.0.1",
"crypto-js": "^4.2.0",
"electron-is-dev": "~2.0.0",
"electron-json-storage": "^4.6.0",
"electron-serve": "~1.1.0",
"electron-unhandled": "~4.0.1",
"electron-updater": "^5.3.0",
"electron-window-state": "^5.0.3",
"jszip": "^3.10.1",
"node-fetch": "^2.6.7"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.13",
"@types/crypto-js": "^4.2.2",
"@types/electron-json-storage": "^4.5.4",
"electron": "^26.2.2",
"electron-builder": "~23.6.0",
"electron-rebuild": "^3.2.9",
"typescript": "~5.2.2"
},
"keywords": [
"capacitor",
"electron"
]
}

View File

@@ -1,10 +0,0 @@
/* eslint-disable no-undef */
/* eslint-disable @typescript-eslint/no-var-requires */
const electronPublish = require('electron-publish');
class Publisher extends electronPublish.Publisher {
async upload(task) {
console.log('electron-publisher-custom', task.file);
}
}
module.exports = Publisher;

View File

@@ -1,108 +0,0 @@
import type { CapacitorElectronConfig } from '@capacitor-community/electron';
import { getCapacitorElectronConfig, setupElectronDeepLinking } from '@capacitor-community/electron';
import type { MenuItemConstructorOptions } from 'electron';
import { app, MenuItem } from 'electron';
import electronIsDev from 'electron-is-dev';
import unhandled from 'electron-unhandled';
import { autoUpdater } from 'electron-updater';
import { ElectronCapacitorApp, setupContentSecurityPolicy, setupReloadWatcher } from './setup';
// Graceful handling of unhandled errors.
unhandled({
logger: (error) => {
// Suppress EPIPE errors which are common in AppImages due to console output issues
if (error.message && error.message.includes('EPIPE')) {
return; // Don't log EPIPE errors
}
console.error('Unhandled error:', error);
}
});
// Handle EPIPE errors on stdout/stderr to prevent crashes
process.stdout.on('error', (err) => {
if (err.code === 'EPIPE') {
// Ignore EPIPE errors on stdout
return;
}
console.error('stdout error:', err);
});
process.stderr.on('error', (err) => {
if (err.code === 'EPIPE') {
// Ignore EPIPE errors on stderr
return;
}
console.error('stderr error:', err);
});
// Define our menu templates (these are optional)
const trayMenuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [new MenuItem({ label: 'Quit App', role: 'quit' })];
const appMenuBarMenuTemplate: (MenuItemConstructorOptions | MenuItem)[] = [
{ role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu' },
{ role: 'viewMenu' },
];
// Get Config options from capacitor.config
const capacitorFileConfig: CapacitorElectronConfig = getCapacitorElectronConfig();
// Initialize our app. You can pass menu templates into the app here.
// const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig);
const myCapacitorApp = new ElectronCapacitorApp(capacitorFileConfig, trayMenuTemplate, appMenuBarMenuTemplate);
// If deeplinking is enabled then we will set it up here.
if (capacitorFileConfig.electron?.deepLinkingEnabled) {
setupElectronDeepLinking(myCapacitorApp, {
customProtocol: capacitorFileConfig.electron.deepLinkingCustomProtocol ?? 'mycapacitorapp',
});
}
// If we are in Dev mode, use the file watcher components.
if (electronIsDev) {
setupReloadWatcher(myCapacitorApp);
}
// Configure auto-updater
autoUpdater.on('error', (error) => {
console.log('Auto-updater error (suppressed):', error.message);
// Don't show error dialogs for update check failures
});
// Run Application
(async () => {
// Wait for electron app to be ready.
await app.whenReady();
// Security - Set Content-Security-Policy based on whether or not we are in dev mode.
setupContentSecurityPolicy(myCapacitorApp.getCustomURLScheme());
// Initialize our app, build windows, and load content.
await myCapacitorApp.init();
// Only check for updates in production builds, not in development or AppImage
if (!electronIsDev && !process.env.APPIMAGE) {
try {
autoUpdater.checkForUpdatesAndNotify();
} catch (error) {
console.log('Update check failed (suppressed):', error);
}
}
})();
// Handle when all of our windows are close (platforms have their own expectations).
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit();
}
});
// When the dock icon is clicked.
app.on('activate', async function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (myCapacitorApp.getMainWindow().isDestroyed()) {
await myCapacitorApp.init();
}
});
// Place all ipc or other electron api calls and custom functionality under this line

View File

@@ -1,4 +0,0 @@
require('./rt/electron-rt');
//////////////////////////////
// User Defined Preload scripts below
console.log('User Preload!');

View File

@@ -1,6 +0,0 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const CapacitorCommunitySqlite = require('../../../node_modules/@capacitor-community/sqlite/electron/dist/plugin.js');
module.exports = {
CapacitorCommunitySqlite,
}

View File

@@ -1,88 +0,0 @@
import { randomBytes } from 'crypto';
import { ipcRenderer, contextBridge } from 'electron';
import { EventEmitter } from 'events';
////////////////////////////////////////////////////////
// eslint-disable-next-line @typescript-eslint/no-var-requires
const plugins = require('./electron-plugins');
const randomId = (length = 5) => randomBytes(length).toString('hex');
const contextApi: {
[plugin: string]: { [functionName: string]: () => Promise<any> };
} = {};
Object.keys(plugins).forEach((pluginKey) => {
Object.keys(plugins[pluginKey])
.filter((className) => className !== 'default')
.forEach((classKey) => {
const functionList = Object.getOwnPropertyNames(plugins[pluginKey][classKey].prototype).filter(
(v) => v !== 'constructor'
);
if (!contextApi[classKey]) {
contextApi[classKey] = {};
}
functionList.forEach((functionName) => {
if (!contextApi[classKey][functionName]) {
contextApi[classKey][functionName] = (...args) => ipcRenderer.invoke(`${classKey}-${functionName}`, ...args);
}
});
// Events
if (plugins[pluginKey][classKey].prototype instanceof EventEmitter) {
const listeners: { [key: string]: { type: string; listener: (...args: any[]) => void } } = {};
const listenersOfTypeExist = (type) =>
!!Object.values(listeners).find((listenerObj) => listenerObj.type === type);
Object.assign(contextApi[classKey], {
addListener(type: string, callback: (...args) => void) {
const id = randomId();
// Deduplicate events
if (!listenersOfTypeExist(type)) {
ipcRenderer.send(`event-add-${classKey}`, type);
}
const eventHandler = (_, ...args) => callback(...args);
ipcRenderer.addListener(`event-${classKey}-${type}`, eventHandler);
listeners[id] = { type, listener: eventHandler };
return id;
},
removeListener(id: string) {
if (!listeners[id]) {
throw new Error('Invalid id');
}
const { type, listener } = listeners[id];
ipcRenderer.removeListener(`event-${classKey}-${type}`, listener);
delete listeners[id];
if (!listenersOfTypeExist(type)) {
ipcRenderer.send(`event-remove-${classKey}-${type}`);
}
},
removeAllListeners(type: string) {
Object.entries(listeners).forEach(([id, listenerObj]) => {
if (!type || listenerObj.type === type) {
ipcRenderer.removeListener(`event-${classKey}-${listenerObj.type}`, listenerObj.listener);
ipcRenderer.send(`event-remove-${classKey}-${listenerObj.type}`);
delete listeners[id];
}
});
},
});
}
});
});
contextBridge.exposeInMainWorld('CapacitorCustomPlatform', {
name: 'electron',
plugins: contextApi,
});
////////////////////////////////////////////////////////

View File

@@ -1,233 +0,0 @@
import type { CapacitorElectronConfig } from '@capacitor-community/electron';
import {
CapElectronEventEmitter,
CapacitorSplashScreen,
setupCapacitorElectronPlugins,
} from '@capacitor-community/electron';
import chokidar from 'chokidar';
import type { MenuItemConstructorOptions } from 'electron';
import { app, BrowserWindow, Menu, MenuItem, nativeImage, Tray, session } from 'electron';
import electronIsDev from 'electron-is-dev';
import electronServe from 'electron-serve';
import windowStateKeeper from 'electron-window-state';
import { join } from 'path';
// Define components for a watcher to detect when the webapp is changed so we can reload in Dev mode.
const reloadWatcher = {
debouncer: null,
ready: false,
watcher: null,
};
export function setupReloadWatcher(electronCapacitorApp: ElectronCapacitorApp): void {
reloadWatcher.watcher = chokidar
.watch(join(app.getAppPath(), 'app'), {
ignored: /[/\\]\./,
persistent: true,
})
.on('ready', () => {
reloadWatcher.ready = true;
})
.on('all', (_event, _path) => {
if (reloadWatcher.ready) {
clearTimeout(reloadWatcher.debouncer);
reloadWatcher.debouncer = setTimeout(async () => {
electronCapacitorApp.getMainWindow().webContents.reload();
reloadWatcher.ready = false;
clearTimeout(reloadWatcher.debouncer);
reloadWatcher.debouncer = null;
reloadWatcher.watcher = null;
setupReloadWatcher(electronCapacitorApp);
}, 1500);
}
});
}
// Define our class to manage our app.
export class ElectronCapacitorApp {
private MainWindow: BrowserWindow | null = null;
private SplashScreen: CapacitorSplashScreen | null = null;
private TrayIcon: Tray | null = null;
private CapacitorFileConfig: CapacitorElectronConfig;
private TrayMenuTemplate: (MenuItem | MenuItemConstructorOptions)[] = [
new MenuItem({ label: 'Quit App', role: 'quit' }),
];
private AppMenuBarMenuTemplate: (MenuItem | MenuItemConstructorOptions)[] = [
{ role: process.platform === 'darwin' ? 'appMenu' : 'fileMenu' },
{ role: 'viewMenu' },
];
private mainWindowState;
private loadWebApp;
private customScheme: string;
constructor(
capacitorFileConfig: CapacitorElectronConfig,
trayMenuTemplate?: (MenuItemConstructorOptions | MenuItem)[],
appMenuBarMenuTemplate?: (MenuItemConstructorOptions | MenuItem)[]
) {
this.CapacitorFileConfig = capacitorFileConfig;
this.customScheme = this.CapacitorFileConfig.electron?.customUrlScheme ?? 'capacitor-electron';
if (trayMenuTemplate) {
this.TrayMenuTemplate = trayMenuTemplate;
}
if (appMenuBarMenuTemplate) {
this.AppMenuBarMenuTemplate = appMenuBarMenuTemplate;
}
// Setup our web app loader, this lets us load apps like react, vue, and angular without changing their build chains.
this.loadWebApp = electronServe({
directory: join(app.getAppPath(), 'app'),
scheme: this.customScheme,
});
}
// Helper function to load in the app.
private async loadMainWindow(thisRef: any) {
await thisRef.loadWebApp(thisRef.MainWindow);
}
// Expose the mainWindow ref for use outside of the class.
getMainWindow(): BrowserWindow {
return this.MainWindow;
}
getCustomURLScheme(): string {
return this.customScheme;
}
async init(): Promise<void> {
const icon = nativeImage.createFromPath(
join(app.getAppPath(), 'assets', process.platform === 'win32' ? 'appIcon.ico' : 'appIcon.png')
);
this.mainWindowState = windowStateKeeper({
defaultWidth: 1000,
defaultHeight: 800,
});
// Setup preload script path and construct our main window.
const preloadPath = join(app.getAppPath(), 'build', 'src', 'preload.js');
this.MainWindow = new BrowserWindow({
icon,
show: false,
x: this.mainWindowState.x,
y: this.mainWindowState.y,
width: this.mainWindowState.width,
height: this.mainWindowState.height,
webPreferences: {
nodeIntegration: true,
contextIsolation: true,
// Use preload to inject the electron varriant overrides for capacitor plugins.
// preload: join(app.getAppPath(), "node_modules", "@capacitor-community", "electron", "dist", "runtime", "electron-rt.js"),
preload: preloadPath,
},
});
this.mainWindowState.manage(this.MainWindow);
if (this.CapacitorFileConfig.backgroundColor) {
this.MainWindow.setBackgroundColor(this.CapacitorFileConfig.electron.backgroundColor);
}
// If we close the main window with the splashscreen enabled we need to destory the ref.
this.MainWindow.on('closed', () => {
if (this.SplashScreen?.getSplashWindow() && !this.SplashScreen.getSplashWindow().isDestroyed()) {
this.SplashScreen.getSplashWindow().close();
}
});
// When the tray icon is enabled, setup the options.
if (this.CapacitorFileConfig.electron?.trayIconAndMenuEnabled) {
this.TrayIcon = new Tray(icon);
this.TrayIcon.on('double-click', () => {
if (this.MainWindow) {
if (this.MainWindow.isVisible()) {
this.MainWindow.hide();
} else {
this.MainWindow.show();
this.MainWindow.focus();
}
}
});
this.TrayIcon.on('click', () => {
if (this.MainWindow) {
if (this.MainWindow.isVisible()) {
this.MainWindow.hide();
} else {
this.MainWindow.show();
this.MainWindow.focus();
}
}
});
this.TrayIcon.setToolTip(app.getName());
this.TrayIcon.setContextMenu(Menu.buildFromTemplate(this.TrayMenuTemplate));
}
// Setup the main manu bar at the top of our window.
Menu.setApplicationMenu(Menu.buildFromTemplate(this.AppMenuBarMenuTemplate));
// If the splashscreen is enabled, show it first while the main window loads then switch it out for the main window, or just load the main window from the start.
if (this.CapacitorFileConfig.electron?.splashScreenEnabled) {
this.SplashScreen = new CapacitorSplashScreen({
imageFilePath: join(
app.getAppPath(),
'assets',
this.CapacitorFileConfig.electron?.splashScreenImageName ?? 'splash.png'
),
windowWidth: 400,
windowHeight: 400,
});
this.SplashScreen.init(this.loadMainWindow, this);
} else {
this.loadMainWindow(this);
}
// Security
this.MainWindow.webContents.setWindowOpenHandler((details) => {
if (!details.url.includes(this.customScheme)) {
return { action: 'deny' };
} else {
return { action: 'allow' };
}
});
this.MainWindow.webContents.on('will-navigate', (event, _newURL) => {
if (!this.MainWindow.webContents.getURL().includes(this.customScheme)) {
event.preventDefault();
}
});
// Link electron plugins into the system.
setupCapacitorElectronPlugins();
// When the web app is loaded we hide the splashscreen if needed and show the mainwindow.
this.MainWindow.webContents.on('dom-ready', () => {
if (this.CapacitorFileConfig.electron?.splashScreenEnabled) {
this.SplashScreen.getSplashWindow().hide();
}
if (!this.CapacitorFileConfig.electron?.hideMainWindowOnLaunch) {
this.MainWindow.show();
}
setTimeout(() => {
if (electronIsDev) {
this.MainWindow.webContents.openDevTools();
}
CapElectronEventEmitter.emit('CAPELECTRON_DeeplinkListenerInitialized', '');
}, 400);
});
}
}
// Set a CSP up for our application based on the custom scheme
export function setupContentSecurityPolicy(customScheme: string): void {
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
callback({
responseHeaders: {
...details.responseHeaders,
'Content-Security-Policy': [
electronIsDev
? `default-src ${customScheme}://* 'unsafe-inline' devtools://* 'unsafe-eval' data: https:; style-src ${customScheme}://* 'unsafe-inline' https://fonts.googleapis.com; font-src ${customScheme}://* https://fonts.gstatic.com data:`
: `default-src ${customScheme}://* 'unsafe-inline' data: https:; style-src ${customScheme}://* 'unsafe-inline' https://fonts.googleapis.com; font-src ${customScheme}://* https://fonts.gstatic.com data:`,
],
},
});
});
}

View File

@@ -1,19 +0,0 @@
{
"compileOnSave": true,
"include": ["./src/**/*", "./capacitor.config.ts", "./capacitor.config.js"],
"compilerOptions": {
"outDir": "./build",
"importHelpers": true,
"target": "ES2017",
"module": "CommonJS",
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"typeRoots": ["./node_modules/@types"],
"allowJs": true,
"rootDir": ".",
"skipLibCheck": true,
"strict": false,
"noImplicitAny": false
}
}

View File

@@ -1,155 +0,0 @@
#!/bin/bash
# experiment.sh
# Author: Matthew Raymer
# Description: Build script for TimeSafari Electron application
# This script handles the complete build process for the TimeSafari Electron app,
# including web asset compilation and Capacitor sync.
#
# Build Process:
# 1. Environment setup and dependency checks
# 2. Web asset compilation (Vite)
# 3. Capacitor sync
# 4. Electron start
#
# Dependencies:
# - Node.js and npm
# - TypeScript
# - Vite
# - @capacitor-community/electron
#
# Usage: ./experiment.sh
#
# Exit Codes:
# 1 - Required command not found
# 2 - TypeScript installation failed
# 3 - Build process failed
# 4 - Capacitor sync failed
# 5 - Electron start failed
# Exit on any error
set -e
# ANSI color codes for better output formatting
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')] [INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] [SUCCESS]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] [WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR]${NC} $1"
}
# Function to check if a command exists
check_command() {
if ! command -v "$1" &> /dev/null; then
log_error "$1 is required but not installed."
exit 1
fi
log_info "Found $1: $(command -v "$1")"
}
# Function to measure and log execution time
measure_time() {
local start_time=$(date +%s)
"$@"
local end_time=$(date +%s)
local duration=$((end_time - start_time))
log_success "Completed in ${duration} seconds"
}
# Print build header
echo -e "\n${BLUE}=== TimeSafari Electron Build Process ===${NC}\n"
log_info "Starting build process at $(date)"
# Check required commands
log_info "Checking required dependencies..."
check_command node
check_command npm
check_command git
# Create application data directory
log_info "Setting up application directories..."
mkdir -p ~/.local/share/TimeSafari/timesafari
# Clean up previous builds
log_info "Cleaning previous builds..."
rm -rf dist* || log_warn "No previous builds to clean"
# Set environment variables for the build
log_info "Configuring build environment..."
export VITE_PLATFORM=electron
export VITE_PWA_ENABLED=false
export VITE_DISABLE_PWA=true
export DEBUG_MIGRATIONS=0
# Ensure TypeScript is installed
log_info "Verifying TypeScript installation..."
if [ ! -f "./node_modules/.bin/tsc" ]; then
log_info "Installing TypeScript..."
if ! npm install --save-dev typescript@~5.2.2; then
log_error "TypeScript installation failed!"
exit 2
fi
# Verify installation
if [ ! -f "./node_modules/.bin/tsc" ]; then
log_error "TypeScript installation verification failed!"
exit 2
fi
log_success "TypeScript installed successfully"
else
log_info "TypeScript already installed"
fi
# Get git hash for versioning
GIT_HASH=$(git log -1 --pretty=format:%h)
log_info "Using git hash: ${GIT_HASH}"
# Build web assets
log_info "Building web assets with Vite..."
if ! measure_time env VITE_GIT_HASH="$GIT_HASH" npx vite build --config vite.config.app.electron.mts --mode electron; then
log_error "Web asset build failed!"
exit 3
fi
# Sync with Capacitor
log_info "Syncing with Capacitor..."
if ! measure_time npx cap sync electron; then
log_error "Capacitor sync failed!"
exit 4
fi
# Restore capacitor config
log_info "Restoring capacitor config..."
if ! git checkout electron/capacitor.config.json; then
log_error "Failed to restore capacitor config!"
exit 4
fi
# Start Electron
log_info "Starting Electron..."
cd electron/
if ! measure_time npm run electron:start; then
log_error "Electron start failed!"
exit 5
fi
# Print build summary
log_success "Build and start completed successfully!"
echo -e "\n${GREEN}=== End of Build Process ===${NC}\n"
# Exit with success
exit 0

View File

@@ -18,10 +18,14 @@
case 'capacitor':
import('./src/main.capacitor.ts');
break;
case 'web':
case 'electron':
import('./src/main.electron.ts');
break;
case 'pywebview':
import('./src/main.pywebview.ts');
break;
default:
import('./src/main.web.ts');
break;
}
</script>
</body>

13
ios/.gitignore vendored
View File

@@ -11,16 +11,3 @@ capacitor-cordova-ios-plugins
# Generated Config files
App/App/capacitor.config.json
App/App/config.xml
# User-specific Xcode files
App/App.xcodeproj/xcuserdata/*.xcuserdatad/
App/App.xcodeproj/*.xcuserstate
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
# Generated Icons from capacitor-assets (also Contents.json which is confusing; see BUILDING.md)
App/App/Assets.xcassets/AppIcon.appiconset
App/App/Assets.xcassets/Splash.imageset

View File

@@ -14,7 +14,7 @@
504EC30F1FED79650016851F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 504EC30E1FED79650016851F /* Assets.xcassets */; };
504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; };
50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; };
97EF2DC6FD76C3643D680B8D /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 90DCAFB4D8948F7A50C13800 /* Pods_App.framework */; };
A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@@ -27,9 +27,9 @@
504EC3111FED79650016851F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
504EC3131FED79650016851F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
90DCAFB4D8948F7A50C13800 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E2E9297D5D02C549106C77F9 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = "<group>"; };
EAEC6436E595F7CD3A1C9E96 /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = "<group>"; };
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; };
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = "<group>"; };
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -37,17 +37,17 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
97EF2DC6FD76C3643D680B8D /* Pods_App.framework in Frameworks */,
A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
4B546315E668C7A13939F417 /* Frameworks */ = {
27E2DDA53C4D2A4D1A88CE4A /* Frameworks */ = {
isa = PBXGroup;
children = (
90DCAFB4D8948F7A50C13800 /* Pods_App.framework */,
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -57,8 +57,8 @@
children = (
504EC3061FED79650016851F /* App */,
504EC3051FED79650016851F /* Products */,
BA325FFCDCE8D334E5C7AEBE /* Pods */,
4B546315E668C7A13939F417 /* Frameworks */,
7F8756D8B27F46E3366F6CEA /* Pods */,
27E2DDA53C4D2A4D1A88CE4A /* Frameworks */,
);
sourceTree = "<group>";
};
@@ -85,13 +85,13 @@
path = App;
sourceTree = "<group>";
};
BA325FFCDCE8D334E5C7AEBE /* Pods */ = {
7F8756D8B27F46E3366F6CEA /* Pods */ = {
isa = PBXGroup;
children = (
EAEC6436E595F7CD3A1C9E96 /* Pods-App.debug.xcconfig */,
E2E9297D5D02C549106C77F9 /* Pods-App.release.xcconfig */,
FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */,
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */,
);
path = Pods;
name = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
@@ -101,13 +101,12 @@
isa = PBXNativeTarget;
buildConfigurationList = 504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */;
buildPhases = (
92977BEA1068CC097A57FC77 /* [CP] Check Pods Manifest.lock */,
6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */,
504EC3001FED79650016851F /* Sources */,
504EC3011FED79650016851F /* Frameworks */,
504EC3021FED79650016851F /* Resources */,
9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */,
012076E8FFE4BF260A79B034 /* Fix Privacy Manifest */,
3525031ED1C96EF4CF6E9959 /* [CP] Embed Pods Frameworks */,
96A7EF592DF3366D00084D51 /* Fix Privacy Manifest */,
);
buildRules = (
);
@@ -187,10 +186,28 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PROJECT_DIR}/app_privacy_manifest_fixer/fixer.sh\" \n";
shellScript = "\"${PROJECT_DIR}/app_privacy_manifest_fixer/fixer.sh\" ";
showEnvVarsInLog = 0;
};
3525031ED1C96EF4CF6E9959 /* [CP] Embed Pods Frameworks */ = {
6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-App-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -205,47 +222,6 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App/Pods-App-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
92977BEA1068CC097A57FC77 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-App-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
96A7EF592DF3366D00084D51 /* Fix Privacy Manifest */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
);
name = "Fix Privacy Manifest";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "$PROJECT_DIR/app_privacy_manifest_fixer/fixer.sh\n";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -399,12 +375,11 @@
};
504EC3171FED79650016851F /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = EAEC6436E595F7CD3A1C9E96 /* Pods-App.debug.xcconfig */;
baseConfigurationReference = FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 35;
DEVELOPMENT_TEAM = GM3FS5JQPH;
CURRENT_PROJECT_VERSION = 18;
ENABLE_APP_SANDBOX = NO;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
INFOPLIST_FILE = App/Info.plist;
@@ -413,7 +388,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.2;
MARKETING_VERSION = 0.4.7;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -426,12 +401,11 @@
};
504EC3181FED79650016851F /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = E2E9297D5D02C549106C77F9 /* Pods-App.release.xcconfig */;
baseConfigurationReference = AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 35;
DEVELOPMENT_TEAM = GM3FS5JQPH;
CURRENT_PROJECT_VERSION = 18;
ENABLE_APP_SANDBOX = NO;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
INFOPLIST_FILE = App/Info.plist;
@@ -440,7 +414,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.2;
MARKETING_VERSION = 0.4.7;
PRODUCT_BUNDLE_IDENTIFIER = app.timesafari;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";

Some files were not shown because too many files have changed in this diff Show More