Initial (open-source)
This commit is contained in:
commit
4d9fdfc590
3062 changed files with 544438 additions and 0 deletions
121
.circleci/config.yml
Normal file
121
.circleci/config.yml
Normal file
|
@ -0,0 +1,121 @@
|
|||
default_config: &default_config
|
||||
environment:
|
||||
SENTRY_ORG: shift-reset-llc
|
||||
SENTRY_PROJECT: actual
|
||||
YARN_CACHE_FOLDER: ~/.cache/yarn
|
||||
CSC_LINK: ~/windows-shift-reset-llc.p12
|
||||
|
||||
cached_files: &cached_files
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
- node_modules
|
||||
- ./packages/desktop-electron/node_modules
|
||||
- ./packages/loot-core/node_modules
|
||||
- ./mobile/node_modules
|
||||
- ./import-ynab4/node_modules
|
||||
- ./api/node_modules
|
||||
- ./node-libofx/node_modules
|
||||
- ./loot-design/node_modules
|
||||
- ./desktop-client/node_modules
|
||||
key: v3-dependencies-{{ checksum "yarn.lock" }}
|
||||
|
||||
version_tag_only: &version_tag_only
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /^\d+\.\d+\.\d+$/
|
||||
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
win: circleci/windows@2.2.0
|
||||
|
||||
jobs:
|
||||
test:
|
||||
<<: *default_config
|
||||
|
||||
docker:
|
||||
- image: circleci/node:12.13
|
||||
|
||||
working_directory: ~/repo
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v3-dependencies-{{ checksum "yarn.lock" }}
|
||||
|
||||
- run: yarn install --pure-lockfile
|
||||
|
||||
- save_cache:
|
||||
<<: *cached_files
|
||||
|
||||
- run: yarn test
|
||||
|
||||
build_windows:
|
||||
<<: *default_config
|
||||
|
||||
executor:
|
||||
name: win/default
|
||||
|
||||
working_directory: ~/repo
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run:
|
||||
command: npm install -g @sentry/cli --unsafe-perm
|
||||
shell: bash
|
||||
|
||||
- run:
|
||||
command: echo $WINDOWS_CERT | base64 --decode >> ${HOME}/windows-shift-reset-llc.p12
|
||||
shell: bash
|
||||
|
||||
- run:
|
||||
command: yarn install --pure-lockfile
|
||||
shell: bash
|
||||
|
||||
- run:
|
||||
command: ./bin/package --release --version ${CIRCLE_TAG}
|
||||
shell: bash
|
||||
|
||||
build_linux:
|
||||
<<: *default_config
|
||||
|
||||
docker:
|
||||
- image: circleci/node:12.13
|
||||
|
||||
working_directory: ~/repo
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v3-dependencies-{{ checksum "yarn.lock" }}
|
||||
|
||||
- run: yarn install --pure-lockfile
|
||||
|
||||
- run: sudo npm install -g @sentry/cli --unsafe-perm
|
||||
|
||||
- run: ./bin/package --release --version ${CIRCLE_TAG}
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
test:
|
||||
jobs:
|
||||
- test
|
||||
build_version:
|
||||
jobs:
|
||||
- test:
|
||||
<<: *version_tag_only
|
||||
- build_windows:
|
||||
<<: *version_tag_only
|
||||
requires:
|
||||
- test
|
||||
- build_linux:
|
||||
<<: *version_tag_only
|
||||
requires:
|
||||
- test
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
yarn.lock text eol=lf
|
20
.gitignore
vendored
Normal file
20
.gitignore
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
/data/*
|
||||
!data/.gitkeep
|
||||
/data2
|
||||
packages/desktop-electron/client-build
|
||||
packages/desktop-electron/.electron-symbols
|
||||
packages/desktop-electron/dist
|
||||
packages/desktop-electron/loot-core
|
||||
node_modules
|
||||
.DS_Store
|
||||
lerna-debug.log
|
||||
Actual-*
|
||||
.#*
|
||||
**/xcuserdata/*
|
||||
.secret-tokens
|
||||
bundle.desktop.js
|
||||
bundle.desktop.js.map
|
||||
bundle.mobile.js
|
||||
bundle.mobile.js.map
|
||||
.sentryclirc
|
||||
export-2020-01-10.csv
|
70
README.md
Normal file
70
README.md
Normal file
|
@ -0,0 +1,70 @@
|
|||
|
||||
This is the source code for [Actual](https://actualbudget.com), a local-first personal finance tool. It is 100% free and open-source.
|
||||
|
||||
If you are only interested in running the latest version, you don't need this repo. You can get the latest version through npm.
|
||||
|
||||
More docs are available in the [docs](XXX) folder.
|
||||
|
||||
## Installation
|
||||
|
||||
### The easy way: using a server (recommended)
|
||||
|
||||
The easiest way to get Actual running is to use the [actual-sync](XXX) project. That is the server for syncing changes across devices, and it comes with the latest version of Actual. The server will provide both the web project and a server for syncing.
|
||||
|
||||
```
|
||||
git clone XXX
|
||||
cd actual-sync
|
||||
yarn install
|
||||
yarn start
|
||||
```
|
||||
|
||||
Navigate to https://localhost:5006 in your browser and you will see Actual.
|
||||
|
||||
You should deploy the server somewhere so you can access your data from anywhere. See instructions on the [actual-sync](XXX) repo.
|
||||
|
||||
### Without a server
|
||||
|
||||
This will give you a fully local web app without a server. This npm package is the `packages/desktop-client` package in this repo built for production:
|
||||
|
||||
```
|
||||
yarn add @actual-app/web
|
||||
```
|
||||
|
||||
Now you need to serve the files in `node_modules/@actual-app/web/build`. One way to do it:
|
||||
|
||||
```
|
||||
cd node_modules/@actual-app/web/build
|
||||
npx http-server .
|
||||
```
|
||||
|
||||
Navigate to http://localhost:8080 and you should see Actual.
|
||||
|
||||
## Building
|
||||
|
||||
If you want to build the latest version, see [releasing.md](XXX). It provides instructions for building this code into the same artifacts that come from npm.
|
||||
|
||||
## Run locally
|
||||
|
||||
Both the electron and web app can started with a single command. When running in development, it will store data in a `data` directory in the root of the `actual` directory.
|
||||
|
||||
First, make sure to run `yarn install` to install all dependencies.
|
||||
|
||||
In the root of the project:
|
||||
|
||||
```
|
||||
yarn start # Run the electron app
|
||||
yarn start:browser # Run the web app
|
||||
|
||||
```
|
||||
|
||||
## Code structure
|
||||
|
||||
The app is split up into a few packages:
|
||||
|
||||
* loot-core - The core application that runs on any platform
|
||||
* loot-design - The generic design components that make up the UI
|
||||
* desktop-client - The desktop UI
|
||||
* desktop-electron - The desktop app
|
||||
* mobile - The mobile app
|
||||
|
||||
More docs are available in the [docs](XXX) folder.
|
11
bin/format-staged
Executable file
11
bin/format-staged
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/bin/sh
|
||||
FILES=$(git diff --cached --name-only --diff-filter=ACMR "*.js" "*.jsx" | sed 's| |\\ |g')
|
||||
[ -z "$FILES" ] && exit 0
|
||||
|
||||
# Prettify all selected files
|
||||
echo "$FILES" | xargs ./node_modules/.bin/prettier --write
|
||||
|
||||
# Add back the modified/prettified files to staging
|
||||
echo "$FILES" | xargs git add
|
||||
|
||||
exit 0
|
27
bin/import-open-source-package
Executable file
27
bin/import-open-source-package
Executable file
|
@ -0,0 +1,27 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
ROOT=$(cd "`dirname $0`"; pwd)
|
||||
NPM_NAME="$1"
|
||||
NAME="$2"
|
||||
PACKAGE_DIR="`dirname "$ROOT"`/packages/$NAME"
|
||||
|
||||
if [ -z "$NAME" ] || [ -z "$NPM_NAME" ]; then
|
||||
echo "Usage: `basename $0` <npm-name> <local-name>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -d "$PACKAGE_DIR" ]; then
|
||||
read -p "Package exists, remove $PACKAGE_DIR? [y/N] " -r
|
||||
if [ -z "$REPLY" ] || [ "$REPLY" != "y" ]; then
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -rf "$PACKAGE_DIR"
|
||||
URL="`npm view "$NPM_NAME" dist.tarball`"
|
||||
TMPDIR="`mktemp -d`"
|
||||
|
||||
cd "$TMPDIR"
|
||||
wget -O tar.tgz "$URL"
|
||||
tar xvzf tar.tgz
|
||||
mv package "$PACKAGE_DIR"
|
50
bin/make-release
Executable file
50
bin/make-release
Executable file
|
@ -0,0 +1,50 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
VERSION=""
|
||||
|
||||
POSITIONAL=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
key="$1"
|
||||
|
||||
case $key in
|
||||
--version)
|
||||
VERSION="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
POSITIONAL+=("$1")
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
set -- "${POSITIONAL[@]}"
|
||||
|
||||
NOTES="$@"
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "--version is required";
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Version: $VERSION"
|
||||
echo "Notes: $NOTES"
|
||||
read -p "Make release? [y/N] " -r
|
||||
if [ -z "$REPLY" ] || [ "$REPLY" != "y" ]; then
|
||||
exit 2
|
||||
fi
|
||||
|
||||
source ./.secret-tokens
|
||||
|
||||
# Tag and push to make windows and linux versions
|
||||
git push origin master
|
||||
git tag -a "$VERSION" -m "$NOTES"
|
||||
git push origin "$VERSION"
|
||||
|
||||
# Make a macOS version
|
||||
./bin/package --release --version "$VERSION"
|
||||
|
||||
# TODO: browser version
|
||||
|
||||
# Finally, update github issues
|
||||
curl -X POST -H "x-release-token: $RELEASE_TOKEN" https://actual-automoto.fly.dev/release/"$VERSION"
|
174
bin/package
Executable file
174
bin/package
Executable file
|
@ -0,0 +1,174 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
|
||||
ROOT=`dirname $0`
|
||||
VERSION=""
|
||||
BETA=""
|
||||
RELEASE=""
|
||||
RELEASE_NOTES=""
|
||||
|
||||
cd "$ROOT/.."
|
||||
|
||||
POSITIONAL=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
key="$1"
|
||||
|
||||
case $key in
|
||||
--version)
|
||||
VERSION="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
--beta)
|
||||
RELEASE="beta"
|
||||
shift
|
||||
;;
|
||||
--release)
|
||||
RELEASE="production"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
POSITIONAL+=("$1")
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
set -- "${POSITIONAL[@]}"
|
||||
|
||||
if [ -z "$VERSION" ] && [ -n "$RELEASE" ]; then
|
||||
echo "Version is required if making a release"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$RELEASE" ]; then
|
||||
if [ -z "$CIRCLE_TAG" ]; then
|
||||
read -p "Make release: $RELEASE v$VERSION? [y/N] " -r
|
||||
if [ -z "$REPLY" ] || [ "$REPLY" != "y" ]; then
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$RELEASE" == "production" ]; then
|
||||
if [ -z "$CIRCLE_TAG" ]; then
|
||||
RELEASE_NOTES=`git tag -l --format="%(contents:subject)" "$VERSION"`
|
||||
else
|
||||
RELEASE_NOTES=`git tag -l --format="%(contents:subject)" "$CIRCLE_TAG"`
|
||||
fi
|
||||
fi
|
||||
|
||||
PACKAGE_VERSION=`node -p -e "require('./packages/desktop-electron/package.json').version"`
|
||||
if [ "$VERSION" != "$PACKAGE_VERSION" ]; then
|
||||
echo "Version in desktop-electron/package.json does not match given version! ($PACKAGE_VERSION)"
|
||||
exit 4
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$OSTYPE" == "msys" ]; then
|
||||
if [ -z "$CIRCLE_TAG" ]; then
|
||||
read -s -p "Windows certificate password: " -r CSC_KEY_PASSWORD
|
||||
export CSC_KEY_PASSWORD
|
||||
else
|
||||
certutil -f -p ${CSC_KEY_PASSWORD} -importPfx ~/windows-shift-reset-llc.p12
|
||||
fi
|
||||
fi
|
||||
|
||||
# We only need to run linting once (and this doesn't seem to work on
|
||||
# Windows for some reason)
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
yarn lint
|
||||
fi
|
||||
|
||||
./node_modules/.bin/patch-package
|
||||
|
||||
(
|
||||
cd packages/loot-design;
|
||||
../../node_modules/.bin/patch-package
|
||||
)
|
||||
|
||||
(
|
||||
cd packages/mobile;
|
||||
../../node_modules/.bin/patch-package
|
||||
)
|
||||
|
||||
(
|
||||
cd packages/loot-core;
|
||||
NODE_ENV=production yarn build:node
|
||||
)
|
||||
|
||||
(
|
||||
cd packages/desktop-client;
|
||||
yarn build
|
||||
)
|
||||
|
||||
rm -fr packages/desktop-electron/client-build
|
||||
cp -r packages/desktop-client/build packages/desktop-electron/client-build
|
||||
# Remove the embedded backend for the browser version. Will improve
|
||||
# this process
|
||||
rm -fr packages/desktop-electron/client-build/data
|
||||
rm -fr packages/desktop-electron/client-build/*kcab.*
|
||||
rm -fr packages/desktop-electron/client-build/*.wasm
|
||||
rm -fr packages/desktop-electron/client-build/*.map
|
||||
|
||||
|
||||
if [ -n "$RELEASE" ]; then
|
||||
SENTRY_CLI="./packages/desktop-electron/node_modules/.bin/sentry-cli"
|
||||
"$SENTRY_CLI" releases -o shift-reset-llc -p actual set-commits "$VERSION" --auto
|
||||
|
||||
echo "Uploading frontend sourcemaps to sentry for version $VERSION..."
|
||||
"$SENTRY_CLI" releases -o shift-reset-llc -p actual files "$VERSION" \
|
||||
upload-sourcemaps --url-prefix app://actual/static/js/ --rewrite ./packages/desktop-client/build/static/js
|
||||
|
||||
echo "Uploading backend sourcemaps to sentry for version $VERSION..."
|
||||
"$SENTRY_CLI" releases -o shift-reset-llc -p actual files "$VERSION" \
|
||||
delete app:///node_modules/loot-core/lib-dist/bundle.desktop.js
|
||||
|
||||
"$SENTRY_CLI" releases -o shift-reset-llc -p actual files "$VERSION" \
|
||||
delete app:///node_modules/loot-core/lib-dist/bundle.desktop.js.map
|
||||
|
||||
"$SENTRY_CLI" releases -o shift-reset-llc -p actual files "$VERSION" \
|
||||
upload-sourcemaps --url-prefix 'app:///node_modules/loot-core/lib-dist/' ./packages/loot-core/lib-dist/bundle.desktop*
|
||||
fi
|
||||
|
||||
(
|
||||
cd packages/desktop-electron;
|
||||
rm -rf dist;
|
||||
|
||||
export npm_config_better_sqlite3_binary_host="https://static.actualbudget.com/prebuild/better-sqlite3"
|
||||
|
||||
if [ "$RELEASE" == "production" ]; then
|
||||
if [ -f ../../.secret-tokens ]; then
|
||||
source ../../.secret-tokens
|
||||
fi
|
||||
./node_modules/.bin/electron-builder --publish always -c.releaseInfo.releaseNotes="$RELEASE_NOTES" --arm64 --x64
|
||||
|
||||
echo "\nCreated release $VERSION with release notes \"$RELEASE_NOTES\""
|
||||
elif [ "$RELEASE" == "beta" ]; then
|
||||
./node_modules/.bin/electron-builder --publish never --arm64 --x64
|
||||
|
||||
WINDOWS_FILE="./dist/Actual Setup $VERSION.exe"
|
||||
if [ -f "$WINDOWS_FILE" ]; then
|
||||
scp "$WINDOWS_FILE" "jlongster.com:/sites/static.actualbudget/Actual-Setup-$VERSION.exe"
|
||||
echo "Windows: https://static.actualbudget.com/Actual-Setup-$VERSION.exe"
|
||||
else
|
||||
echo "No Windows file found"
|
||||
fi
|
||||
|
||||
MAC_FILE="./dist/Actual-$VERSION.dmg"
|
||||
if [ -f "$MAC_FILE" ]; then
|
||||
scp "$MAC_FILE" "jlongster.com:/sites/static.actualbudget.com/Actual-$VERSION.dmg"
|
||||
echo "macOS: https://static.actualbudget.com/Actual-$VERSION.dmg"
|
||||
else
|
||||
echo "No macOS file found"
|
||||
fi
|
||||
|
||||
LINUX_FILE="./dist/Actual-$VERSION-x86_64.AppImage"
|
||||
if [ -f "$LINUX_FILE" ]; then
|
||||
scp "$LINUX_FILE" "jlongster.com:/sites/static.actualbudget/Actual-$VERSION-x86_64.AppImage"
|
||||
echo "Linux: https://static.actualbudget.com/Actual-$VERSION-x86_64.AppImage"
|
||||
else
|
||||
echo "No linux file found"
|
||||
fi
|
||||
else
|
||||
SKIP_NOTARIZATION=true ./node_modules/.bin/electron-builder --publish never --x64
|
||||
fi
|
||||
)
|
70
bin/package-browser
Executable file
70
bin/package-browser
Executable file
|
@ -0,0 +1,70 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
ROOT=`dirname $0`
|
||||
VERSION=""
|
||||
RELEASE=""
|
||||
|
||||
cd "$ROOT/.."
|
||||
|
||||
POSITIONAL=()
|
||||
while [[ $# -gt 0 ]]; do
|
||||
key="$1"
|
||||
|
||||
case $key in
|
||||
--version)
|
||||
VERSION="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
--beta)
|
||||
RELEASE="beta"
|
||||
shift
|
||||
;;
|
||||
--release)
|
||||
RELEASE="production"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
POSITIONAL+=("$1")
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
set -- "${POSITIONAL[@]}"
|
||||
|
||||
if [ -z "$VERSION" ] && [ -n "$RELEASE" ]; then
|
||||
echo "Version is required if making a release"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$RELEASE" ]; then
|
||||
read -p "Deploy release for browser: $RELEASE v$VERSION? [y/N] " -r
|
||||
if [ -z "$REPLY" ] || [ "$REPLY" != "y" ]; then
|
||||
exit 2
|
||||
fi
|
||||
|
||||
PACKAGE_VERSION=`node -p -e "require('./packages/desktop-electron/package.json').version"`
|
||||
if [ "$VERSION" != "$PACKAGE_VERSION" ] && [ "$VERSION-next" != "$PACKAGE_VERSION" ]; then
|
||||
echo "Version in desktop-electron/package.json does not match given version! ($PACKAGE_VERSION)"
|
||||
exit 4
|
||||
fi
|
||||
fi
|
||||
|
||||
yarn lint
|
||||
|
||||
(
|
||||
cd packages/loot-design;
|
||||
../../node_modules/.bin/patch-package
|
||||
)
|
||||
|
||||
(
|
||||
cd packages/loot-core;
|
||||
ACTUAL_RELEASE_TYPE=$RELEASE yarn build:browser
|
||||
)
|
||||
|
||||
(
|
||||
cd packages/desktop-client;
|
||||
REACT_APP_RELEASE_TYPE=$RELEASE yarn build:browser
|
||||
)
|
||||
|
||||
echo "packages/desktop-client/build"
|
0
data/.gitkeep
Normal file
0
data/.gitkeep
Normal file
20
docs/API.md
Normal file
20
docs/API.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
|
||||
Previous docs for the API are [here](https://actualbudget.com/docs/developers/using-the-API/). The API is currently being improved. Previously, the API connected to an existing running instance of Actual. Now the API is bundled and fully isolated, capable of running all of Actual itself. Setting up the API is different because of this.
|
||||
|
||||
You need to call `init` and pass it the directory where your files live. Call `load-budget` to load the file you want to work on. After that, you can use the same API as before.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
let actual = require('@actual-app/api');
|
||||
|
||||
await actual.init({
|
||||
config: {
|
||||
dataDir: join(__dirname, 'user-files')
|
||||
}
|
||||
});
|
||||
|
||||
await actual.internal.send('load-budget', { id: 'My-Finances' });
|
||||
|
||||
await actual.getAccounts();
|
||||
```
|
44
docs/releasing.md
Normal file
44
docs/releasing.md
Normal file
|
@ -0,0 +1,44 @@
|
|||
# How to cut a release
|
||||
|
||||
In the open-source version of Actual, all updates go through npm. There are two libraries:
|
||||
|
||||
* `@actual-app/api`: The API for the underlying functionality. This includes the entire backend of Actual, meant to be used with node.
|
||||
* `@actual-app/web`: A web build that will serve the app with a web frontend. This includes both the frontend and backend of Actual. It includes the backend as well because it's built to be used as a Web Worker.
|
||||
|
||||
Both the API and web libraries are versioned together. This may change in the future, but because the web library also brings along its own backend it's easier to maintain a single version for now. That makes it clear which version the backend is regardless of library.
|
||||
|
||||
## Releasing `@actual-app/api`
|
||||
|
||||
This generates a bundle for the API:
|
||||
|
||||
```
|
||||
cd packages/loot-core
|
||||
yarn build:api
|
||||
```
|
||||
|
||||
The built files live in `lib-dist`, so we need to copy them to the API package:
|
||||
|
||||
```
|
||||
cp lib-dist/bundle.api* ../api/app
|
||||
```
|
||||
|
||||
Next, bump the version on package.json. Finally, publish it:
|
||||
|
||||
```
|
||||
npm publish
|
||||
```
|
||||
|
||||
## Releasing `@actual-app/web`
|
||||
|
||||
In the root of `actual` (not just `desktop-client`), run this:
|
||||
|
||||
```
|
||||
./bin/package-browser
|
||||
```
|
||||
|
||||
This will compile both the backend and the frontend into a single directory in `packages/desktop-client/build`. This directory is all the files that need to be published. After bumping the version, publish `desktop-client`:
|
||||
|
||||
```
|
||||
cd packages/desktop-client
|
||||
npm publish
|
||||
```
|
24
jest-babel-transformer-esm.js
Normal file
24
jest-babel-transformer-esm.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
const { join, resolve } = require('path');
|
||||
const { createTransformer } = require('babel-jest');
|
||||
const packagePath = resolve('./');
|
||||
|
||||
const packageGlob = join(packagePath, 'packages/*');
|
||||
|
||||
module.exports = createTransformer({
|
||||
babelrcRoots: packageGlob,
|
||||
|
||||
// TODO: This is awful and a mess and we should fix it.
|
||||
//
|
||||
// Forcing this on allows certain packages in node_modules to be
|
||||
// exported as ESM, which jest usually errors on. node_modules are
|
||||
// usually not transformed, but you can allowlist one in the
|
||||
// `jest.config.js` for your project like this:
|
||||
//
|
||||
// transformIgnorePatterns: [
|
||||
// '/node_modules/(?!absurd-sql)'
|
||||
// ],
|
||||
//
|
||||
// Without this explicit plugin, even though Jest transforms the
|
||||
// module it won't recognize ESM
|
||||
plugins: ['@babel/plugin-transform-modules-commonjs']
|
||||
});
|
9
jest-babel-transformer.js
Normal file
9
jest-babel-transformer.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
const { join, resolve } = require('path');
|
||||
const { createTransformer } = require('babel-jest');
|
||||
const packagePath = resolve('./');
|
||||
|
||||
const packageGlob = join(packagePath, 'packages/*');
|
||||
|
||||
module.exports = createTransformer({
|
||||
babelrcRoots: packageGlob
|
||||
});
|
8
jest.config.js
Normal file
8
jest.config.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
module.exports = {
|
||||
projects: [
|
||||
'<rootDir>/packages/loot-core',
|
||||
'<rootDir>/packages/loot-core/jest.web.config.js',
|
||||
'<rootDir>/packages/loot-design',
|
||||
'<rootDir>/packages/loot-design/jest.rn.config.js'
|
||||
]
|
||||
};
|
81
package.json
Normal file
81
package.json
Normal file
|
@ -0,0 +1,81 @@
|
|||
{
|
||||
"name": "actual",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"nohoist": [
|
||||
"**/better-sqlite3",
|
||||
"**/better-sqlite3/**",
|
||||
"mobile/react-native",
|
||||
"mobile/react-native-*",
|
||||
"mobile/rn-fetch-blob",
|
||||
"mobile/**/event-target-shim",
|
||||
"mobile/@sentry/react-native",
|
||||
"mobile/nodejs-mobile-react-native",
|
||||
"**/mobile/nodejs-mobile-react-native/**",
|
||||
"**/@react-native-community/**",
|
||||
"**/@react-navigation/**",
|
||||
"mobile/react-navigation",
|
||||
"mobile/react-navigation-tabs",
|
||||
"mobile/rn-snoopy",
|
||||
"mobile/rn-snoopy/**",
|
||||
"mobile/detox",
|
||||
"mobile/detox/**",
|
||||
"mobile/jsc-android",
|
||||
"mobile/jsc-android/**",
|
||||
"**/react-native-web",
|
||||
"**/react-native-web/**",
|
||||
"**/@sentry/cli"
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"bootstrap": "./bootstrap",
|
||||
"start": "npm-run-all --parallel start:desktop-*",
|
||||
"start:desktop-node": "cd packages/loot-core && yarn watch:node",
|
||||
"start:desktop-client": "cd packages/desktop-client && yarn watch",
|
||||
"start:desktop-electron": "cd packages/desktop-electron && yarn watch",
|
||||
"start:browser": "npm-run-all --parallel start:browser-*",
|
||||
"start:browser-backend": "cd packages/loot-core && yarn watch:browser",
|
||||
"start:browser-frontend": "cd packages/desktop-client && yarn start:browser",
|
||||
"test": "./node_modules/.bin/jest --maxWorkers=4",
|
||||
"test:debug": "node ./node_modules/.bin/jest --runInBand --useStderr",
|
||||
"test:debug-brk": "node --inspect-brk ./node_modules/.bin/jest --runInBand",
|
||||
"make-test-db": "./packages/loot-core/src/bin/make-test-db ./data",
|
||||
"make-fresh-db": "./packages/loot-core/src/bin/migrate reset --db ./data/user1/db.sqlite",
|
||||
"rebuild-electron": "./node_modules/.bin/electron-rebuild -f -m ./packages/loot-core",
|
||||
"rebuild-node": "cd packages/loot-core && npm rebuild",
|
||||
"lint": "cd packages/loot-core && yarn lint",
|
||||
"postinstall": "rm -rf ./packages/loot-design/node_modules/react && rm -rf ./packages/mobile/node_modules/react && rm -rf ./node_modules/react-native && patch-package"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.15.0",
|
||||
"cross-env": "^5.1.5",
|
||||
"husky": "^3.0.4",
|
||||
"npm-run-all": "^4.1.3",
|
||||
"prettier": "^1.18.1",
|
||||
"pretty-quick": "^1.11.1",
|
||||
"shelljs": "^0.8.2",
|
||||
"source-map-support": "^0.5.21"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app",
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
"no-loop-func": "off",
|
||||
"no-restricted-globals": "off"
|
||||
}
|
||||
},
|
||||
"prettier": {
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none"
|
||||
},
|
||||
"resolutions": {
|
||||
"@babel/preset-env": "^7.15.1",
|
||||
"@babel/core": "^7.15.1",
|
||||
"@babel/runtime": "^7.15.1",
|
||||
"@babel/helper-plugin-utils": "^7.14.5"
|
||||
}
|
||||
}
|
5
packages/api/README.md
Normal file
5
packages/api/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
```
|
||||
npm install @actual-app/api
|
||||
```
|
||||
|
||||
View docs here: https://actualbudget.com/docs/developers/using-the-API/
|
69607
packages/api/app/bundle.api.js
Normal file
69607
packages/api/app/bundle.api.js
Normal file
File diff suppressed because one or more lines are too long
1
packages/api/app/bundle.api.js.map
Normal file
1
packages/api/app/bundle.api.js.map
Normal file
File diff suppressed because one or more lines are too long
122
packages/api/app/query.js
Normal file
122
packages/api/app/query.js
Normal file
|
@ -0,0 +1,122 @@
|
|||
class Query {
|
||||
constructor(state) {
|
||||
this.state = {
|
||||
filterExpressions: state.filterExpressions || [],
|
||||
selectExpressions: state.selectExpressions || [],
|
||||
groupExpressions: state.groupExpressions || [],
|
||||
orderExpressions: state.orderExpressions || [],
|
||||
calculation: false,
|
||||
rawMode: false,
|
||||
withDead: false,
|
||||
validateRefs: true,
|
||||
limit: null,
|
||||
offset: null,
|
||||
...state
|
||||
};
|
||||
}
|
||||
|
||||
filter(expr) {
|
||||
return new Query({
|
||||
...this.state,
|
||||
filterExpressions: [...this.state.filterExpressions, expr]
|
||||
});
|
||||
}
|
||||
|
||||
unfilter(exprs) {
|
||||
let exprSet = new Set(exprs);
|
||||
return new Query({
|
||||
...this.state,
|
||||
filterExpressions: this.state.filterExpressions.filter(
|
||||
expr => !exprSet.has(Object.keys(expr)[0])
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
select(exprs = []) {
|
||||
if (!Array.isArray(exprs)) {
|
||||
exprs = [exprs];
|
||||
}
|
||||
|
||||
let query = new Query({ ...this.state, selectExpressions: exprs });
|
||||
query.state.calculation = false;
|
||||
return query;
|
||||
}
|
||||
|
||||
calculate(expr) {
|
||||
let query = this.select({ result: expr });
|
||||
query.state.calculation = true;
|
||||
return query;
|
||||
}
|
||||
|
||||
groupBy(exprs) {
|
||||
if (!Array.isArray(exprs)) {
|
||||
exprs = [exprs];
|
||||
}
|
||||
|
||||
return new Query({
|
||||
...this.state,
|
||||
groupExpressions: [...this.state.groupExpressions, ...exprs]
|
||||
});
|
||||
}
|
||||
|
||||
orderBy(exprs) {
|
||||
if (!Array.isArray(exprs)) {
|
||||
exprs = [exprs];
|
||||
}
|
||||
|
||||
return new Query({
|
||||
...this.state,
|
||||
orderExpressions: [...this.state.orderExpressions, ...exprs]
|
||||
});
|
||||
}
|
||||
|
||||
limit(num) {
|
||||
return new Query({ ...this.state, limit: num });
|
||||
}
|
||||
|
||||
offset(num) {
|
||||
return new Query({ ...this.state, offset: num });
|
||||
}
|
||||
|
||||
raw() {
|
||||
return new Query({ ...this.state, rawMode: true });
|
||||
}
|
||||
|
||||
withDead() {
|
||||
return new Query({ ...this.state, withDead: true });
|
||||
}
|
||||
|
||||
withoutValidatedRefs() {
|
||||
return new Query({ ...this.state, validateRefs: false });
|
||||
}
|
||||
|
||||
options(opts) {
|
||||
return new Query({ ...this.state, tableOptions: opts });
|
||||
}
|
||||
|
||||
serialize() {
|
||||
return this.state;
|
||||
}
|
||||
}
|
||||
|
||||
function getPrimaryOrderBy(query, defaultOrderBy) {
|
||||
let orderExprs = query.serialize().orderExpressions;
|
||||
if (orderExprs.length === 0) {
|
||||
if (defaultOrderBy) {
|
||||
return { order: 'asc', ...defaultOrderBy };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
let firstOrder = orderExprs[0];
|
||||
if (typeof firstOrder === 'string') {
|
||||
return { field: firstOrder, order: 'asc' };
|
||||
}
|
||||
// Handle this form: { field: 'desc' }
|
||||
let [field] = Object.keys(firstOrder);
|
||||
return { field, order: firstOrder[field] };
|
||||
}
|
||||
|
||||
module.exports = function q(table) {
|
||||
return new Query({ table });
|
||||
};
|
BIN
packages/api/default-db.sqlite
Normal file
BIN
packages/api/default-db.sqlite
Normal file
Binary file not shown.
35
packages/api/index.js
Normal file
35
packages/api/index.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
let bundle = require('./app/bundle.api.js');
|
||||
let methods = require('./methods');
|
||||
let utils = require('./utils');
|
||||
|
||||
let injected = require('./injected');
|
||||
let actualApp;
|
||||
|
||||
async function init({ budgetId, config } = {}) {
|
||||
if (actualApp) {
|
||||
return;
|
||||
}
|
||||
|
||||
global.fetch = require('node-fetch');
|
||||
|
||||
await bundle.init({ budgetId, config });
|
||||
actualApp = bundle.lib;
|
||||
|
||||
injected.send = bundle.lib.send;
|
||||
return bundle.lib;
|
||||
}
|
||||
|
||||
async function shutdown() {
|
||||
if (actualApp) {
|
||||
await actualApp.send('close-budget');
|
||||
actualApp = null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init,
|
||||
shutdown,
|
||||
utils,
|
||||
internal: bundle.lib,
|
||||
...methods
|
||||
};
|
5
packages/api/injected.js
Normal file
5
packages/api/injected.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
// TODO: comment on why it works this way
|
||||
|
||||
let send;
|
||||
|
||||
module.exports = { send };
|
211
packages/api/methods.js
Normal file
211
packages/api/methods.js
Normal file
|
@ -0,0 +1,211 @@
|
|||
const q = require('./app/query');
|
||||
const injected = require('./injected');
|
||||
|
||||
function send(name, args) {
|
||||
return injected.send(name, args);
|
||||
}
|
||||
|
||||
async function runImport(name, func) {
|
||||
await send('api/start-import', { budgetName: name });
|
||||
try {
|
||||
await func();
|
||||
} catch (e) {
|
||||
await send('api/abort-import');
|
||||
throw e;
|
||||
}
|
||||
await send('api/finish-import');
|
||||
}
|
||||
|
||||
async function loadBudget(budgetId) {
|
||||
return send('api/load-budget', { id: budgetId });
|
||||
}
|
||||
|
||||
async function batchBudgetUpdates(func) {
|
||||
await send('api/batch-budget-start');
|
||||
try {
|
||||
await func();
|
||||
} finally {
|
||||
await send('api/batch-budget-end');
|
||||
}
|
||||
}
|
||||
|
||||
function runQuery(query) {
|
||||
return send('api/query', { query: query.serialize() });
|
||||
}
|
||||
|
||||
function getBudgetMonths() {
|
||||
return send('api/budget-months');
|
||||
}
|
||||
|
||||
function getBudgetMonth(month) {
|
||||
return send('api/budget-month', { month });
|
||||
}
|
||||
|
||||
function setBudgetAmount(month, categoryId, value) {
|
||||
return send('api/budget-set-amount', { month, categoryId, amount: value });
|
||||
}
|
||||
|
||||
function setBudgetCarryover(month, categoryId, flag) {
|
||||
return send('api/budget-set-carryover', { month, categoryId, flag });
|
||||
}
|
||||
|
||||
function addTransactions(accountId, transactions) {
|
||||
return send('api/transactions-add', { accountId, transactions });
|
||||
}
|
||||
|
||||
function importTransactions(accountId, transactions) {
|
||||
return send('api/transactions-import', { accountId, transactions });
|
||||
}
|
||||
|
||||
function getTransactions(accountId, startDate, endDate) {
|
||||
return send('api/transactions-get', { accountId, startDate, endDate });
|
||||
}
|
||||
|
||||
function filterTransactions(accountId, text) {
|
||||
return send('api/transactions-filter', { accountId, text });
|
||||
}
|
||||
|
||||
function updateTransaction(id, fields) {
|
||||
return send('api/transaction-update', { id, fields });
|
||||
}
|
||||
|
||||
function deleteTransaction(id) {
|
||||
return send('api/transaction-delete', { id });
|
||||
}
|
||||
|
||||
function getAccounts() {
|
||||
return send('api/accounts-get');
|
||||
}
|
||||
|
||||
function createAccount(account, initialBalance) {
|
||||
return send('api/account-create', { account, initialBalance });
|
||||
}
|
||||
|
||||
function updateAccount(id, fields) {
|
||||
return send('api/account-update', { id, fields });
|
||||
}
|
||||
|
||||
function closeAccount(id, transferAccountId, transferCategoryId) {
|
||||
return send('api/account-close', {
|
||||
id,
|
||||
transferAccountId,
|
||||
transferCategoryId
|
||||
});
|
||||
}
|
||||
|
||||
function reopenAccount(id) {
|
||||
return send('api/account-reopen', { id });
|
||||
}
|
||||
|
||||
function deleteAccount(id) {
|
||||
return send('api/account-delete', { id });
|
||||
}
|
||||
|
||||
function getCategoryGroups() {
|
||||
return send('api/categories-get', { grouped: true });
|
||||
}
|
||||
|
||||
function createCategoryGroup(group) {
|
||||
return send('api/category-group-create', { group });
|
||||
}
|
||||
|
||||
function updateCategoryGroup(id, fields) {
|
||||
return send('api/category-group-update', { id, fields });
|
||||
}
|
||||
|
||||
function deleteCategoryGroup(id, transferCategoryId) {
|
||||
return send('api/category-group-delete', { id, transferCategoryId });
|
||||
}
|
||||
|
||||
function getCategories() {
|
||||
return send('api/categories-get', { grouped: false });
|
||||
}
|
||||
|
||||
function createCategory(category) {
|
||||
return send('api/category-create', { category });
|
||||
}
|
||||
|
||||
function updateCategory(id, fields) {
|
||||
return send('api/category-update', { id, fields });
|
||||
}
|
||||
|
||||
function deleteCategory(id, transferCategoryId) {
|
||||
return send('api/category-delete', { id, transferCategoryId });
|
||||
}
|
||||
|
||||
function getPayees() {
|
||||
return send('api/payees-get');
|
||||
}
|
||||
|
||||
function createPayee(payee) {
|
||||
return send('api/payee-create', { payee });
|
||||
}
|
||||
|
||||
function updatePayee(id, fields) {
|
||||
return send('api/payee-update', { id, fields });
|
||||
}
|
||||
|
||||
function deletePayee(id) {
|
||||
return send('api/payee-delete', { id });
|
||||
}
|
||||
|
||||
function getPayeeRules(payeeId) {
|
||||
return send('api/payee-rules-get', { payeeId });
|
||||
}
|
||||
|
||||
function createPayeeRule(payeeId, rule) {
|
||||
return send('api/payee-rule-create', { payee_id: payeeId, rule });
|
||||
}
|
||||
|
||||
function updatePayeeRule(id, fields) {
|
||||
return send('api/payee-rule-update', { id, fields });
|
||||
}
|
||||
|
||||
function deletePayeeRule(id) {
|
||||
return send('api/payee-rule-delete', { id });
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
runImport,
|
||||
|
||||
runQuery,
|
||||
q,
|
||||
|
||||
loadBudget,
|
||||
batchBudgetUpdates,
|
||||
getBudgetMonths,
|
||||
getBudgetMonth,
|
||||
setBudgetAmount,
|
||||
setBudgetCarryover,
|
||||
|
||||
addTransactions,
|
||||
importTransactions,
|
||||
filterTransactions,
|
||||
getTransactions,
|
||||
updateTransaction,
|
||||
deleteTransaction,
|
||||
|
||||
getAccounts,
|
||||
createAccount,
|
||||
updateAccount,
|
||||
closeAccount,
|
||||
reopenAccount,
|
||||
deleteAccount,
|
||||
|
||||
getCategories,
|
||||
createCategoryGroup,
|
||||
updateCategoryGroup,
|
||||
deleteCategoryGroup,
|
||||
createCategory,
|
||||
updateCategory,
|
||||
deleteCategory,
|
||||
|
||||
getPayees,
|
||||
createPayee,
|
||||
updatePayee,
|
||||
deletePayee,
|
||||
getPayeeRules,
|
||||
createPayeeRule,
|
||||
deletePayeeRule,
|
||||
updatePayeeRule
|
||||
};
|
0
packages/api/migrations/.force-copy-windows
Normal file
0
packages/api/migrations/.force-copy-windows
Normal file
|
@ -0,0 +1,5 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
DROP TABLE db_version;
|
||||
|
||||
COMMIT;
|
23
packages/api/migrations/1550601598648_payees.sql
Normal file
23
packages/api/migrations/1550601598648_payees.sql
Normal file
|
@ -0,0 +1,23 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE payees
|
||||
(id TEXT PRIMARY KEY,
|
||||
name TEXT,
|
||||
category TEXT,
|
||||
tombstone INTEGER DEFAULT 0,
|
||||
transfer_acct TEXT);
|
||||
|
||||
CREATE TABLE payee_rules
|
||||
(id TEXT PRIMARY KEY,
|
||||
payee_id TEXT,
|
||||
type TEXT,
|
||||
value TEXT,
|
||||
tombstone INTEGER DEFAULT 0);
|
||||
|
||||
CREATE INDEX payee_rules_lowercase_index ON payee_rules(LOWER(value));
|
||||
|
||||
CREATE TABLE payee_mapping
|
||||
(id TEXT PRIMARY KEY,
|
||||
targetId TEXT);
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,25 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TEMPORARY TABLE category_groups_tmp
|
||||
(id TEXT PRIMARY KEY,
|
||||
name TEXT UNIQUE,
|
||||
is_income INTEGER DEFAULT 0,
|
||||
sort_order REAL,
|
||||
tombstone INTEGER DEFAULT 0);
|
||||
|
||||
INSERT INTO category_groups_tmp SELECT * FROM category_groups;
|
||||
|
||||
DROP TABLE category_groups;
|
||||
|
||||
CREATE TABLE category_groups
|
||||
(id TEXT PRIMARY KEY,
|
||||
name TEXT,
|
||||
is_income INTEGER DEFAULT 0,
|
||||
sort_order REAL,
|
||||
tombstone INTEGER DEFAULT 0);
|
||||
|
||||
INSERT INTO category_groups SELECT * FROM category_groups_tmp;
|
||||
|
||||
DROP TABLE category_groups_tmp;
|
||||
|
||||
COMMIT;
|
7
packages/api/migrations/1561751833510_indexes.sql
Normal file
7
packages/api/migrations/1561751833510_indexes.sql
Normal file
|
@ -0,0 +1,7 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE INDEX trans_category_date ON transactions(category, date);
|
||||
CREATE INDEX trans_category ON transactions(category);
|
||||
CREATE INDEX trans_date ON transactions(date);
|
||||
|
||||
COMMIT;
|
38
packages/api/migrations/1567699552727_budget.sql
Normal file
38
packages/api/migrations/1567699552727_budget.sql
Normal file
|
@ -0,0 +1,38 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
DELETE FROM spreadsheet_cells WHERE
|
||||
name NOT LIKE '%!budget\_%' ESCAPE '\' AND
|
||||
name NOT LIKE '%!carryover\_%' ESCAPE '\' AND
|
||||
name NOT LIKE '%!buffered';
|
||||
|
||||
UPDATE OR REPLACE spreadsheet_cells SET name = REPLACE(name, '_', '-');
|
||||
|
||||
UPDATE OR REPLACE spreadsheet_cells SET
|
||||
name =
|
||||
SUBSTR(name, 1, 28) ||
|
||||
'-' ||
|
||||
SUBSTR(name, 29, 4) ||
|
||||
'-' ||
|
||||
SUBSTR(name, 33, 4) ||
|
||||
'-' ||
|
||||
SUBSTR(name, 37, 4) ||
|
||||
'-' ||
|
||||
SUBSTR(name, 41, 12)
|
||||
WHERE name LIKE '%!budget-%' AND LENGTH(name) = 52;
|
||||
|
||||
UPDATE OR REPLACE spreadsheet_cells SET
|
||||
name =
|
||||
SUBSTR(name, 1, 31) ||
|
||||
'-' ||
|
||||
SUBSTR(name, 32, 4) ||
|
||||
'-' ||
|
||||
SUBSTR(name, 36, 4) ||
|
||||
'-' ||
|
||||
SUBSTR(name, 40, 4) ||
|
||||
'-' ||
|
||||
SUBSTR(name, 44, 12)
|
||||
WHERE name LIKE '%!carryover-%' AND LENGTH(name) = 55;
|
||||
|
||||
UPDATE spreadsheet_cells SET expr = SUBSTR(expr, 2) WHERE name LIKE '%!carryover-%';
|
||||
|
||||
COMMIT;
|
6
packages/api/migrations/1582384163573_cleared.sql
Normal file
6
packages/api/migrations/1582384163573_cleared.sql
Normal file
|
@ -0,0 +1,6 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE transactions ADD COLUMN cleared INTEGER DEFAULT 1;
|
||||
ALTER TABLE transactions ADD COLUMN pending INTEGER DEFAULT 0;
|
||||
|
||||
COMMIT;
|
10
packages/api/migrations/1597756566448_rules.sql
Normal file
10
packages/api/migrations/1597756566448_rules.sql
Normal file
|
@ -0,0 +1,10 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE rules
|
||||
(id TEXT PRIMARY KEY,
|
||||
stage TEXT,
|
||||
conditions TEXT,
|
||||
actions TEXT,
|
||||
tombstone INTEGER DEFAULT 0);
|
||||
|
||||
COMMIT;
|
13
packages/api/migrations/1608652596043_parent_field.sql
Normal file
13
packages/api/migrations/1608652596043_parent_field.sql
Normal file
|
@ -0,0 +1,13 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE transactions ADD COLUMN parent_id TEXT;
|
||||
|
||||
UPDATE transactions SET
|
||||
parent_id = CASE
|
||||
WHEN isChild THEN SUBSTR(id, 1, INSTR(id, '/') - 1)
|
||||
ELSE NULL
|
||||
END;
|
||||
|
||||
CREATE INDEX trans_parent_id ON transactions(parent_id);
|
||||
|
||||
COMMIT;
|
56
packages/api/migrations/1608652596044_trans_views.sql
Normal file
56
packages/api/migrations/1608652596044_trans_views.sql
Normal file
|
@ -0,0 +1,56 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
DROP VIEW IF EXISTS v_transactions_layer2;
|
||||
CREATE VIEW v_transactions_layer2 AS
|
||||
SELECT
|
||||
t.id AS id,
|
||||
t.isParent AS is_parent,
|
||||
t.isChild AS is_child,
|
||||
t.acct AS account,
|
||||
CASE WHEN t.isChild = 0 THEN NULL ELSE t.parent_id END AS parent_id,
|
||||
CASE WHEN t.isParent = 1 THEN NULL ELSE cm.transferId END AS category,
|
||||
pm.targetId AS payee,
|
||||
t.imported_description AS imported_payee,
|
||||
IFNULL(t.amount, 0) AS amount,
|
||||
t.notes AS notes,
|
||||
t.date AS date,
|
||||
t.financial_id AS imported_id,
|
||||
t.error AS error,
|
||||
t.starting_balance_flag AS starting_balance_flag,
|
||||
t.transferred_id AS transfer_id,
|
||||
t.sort_order AS sort_order,
|
||||
t.cleared AS cleared,
|
||||
t.tombstone AS tombstone
|
||||
FROM transactions t
|
||||
LEFT JOIN category_mapping cm ON cm.id = t.category
|
||||
LEFT JOIN payee_mapping pm ON pm.id = t.description
|
||||
WHERE
|
||||
t.date IS NOT NULL AND
|
||||
t.acct IS NOT NULL;
|
||||
|
||||
CREATE INDEX trans_sorted ON transactions(date desc, starting_balance_flag, sort_order desc, id);
|
||||
|
||||
DROP VIEW IF EXISTS v_transactions_layer1;
|
||||
CREATE VIEW v_transactions_layer1 AS
|
||||
SELECT t.* FROM v_transactions_layer2 t
|
||||
LEFT JOIN transactions t2 ON (t.is_child = 1 AND t2.id = t.parent_id)
|
||||
WHERE IFNULL(t.tombstone, 0) = 0 AND IFNULL(t2.tombstone, 0) = 0;
|
||||
|
||||
DROP VIEW IF EXISTS v_transactions;
|
||||
CREATE VIEW v_transactions AS
|
||||
SELECT t.* FROM v_transactions_layer1 t
|
||||
ORDER BY t.date desc, t.starting_balance_flag, t.sort_order desc, t.id;
|
||||
|
||||
|
||||
DROP VIEW IF EXISTS v_categories;
|
||||
CREATE VIEW v_categories AS
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
is_income,
|
||||
cat_group AS "group",
|
||||
sort_order,
|
||||
tombstone
|
||||
FROM categories;
|
||||
|
||||
COMMIT;
|
7
packages/api/migrations/1612625548236_optimize.sql
Normal file
7
packages/api/migrations/1612625548236_optimize.sql
Normal file
|
@ -0,0 +1,7 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE INDEX messages_crdt_search ON messages_crdt(dataset, row, column, timestamp);
|
||||
|
||||
ANALYZE;
|
||||
|
||||
COMMIT;
|
33
packages/api/migrations/1614782639336_trans_views2.sql
Normal file
33
packages/api/migrations/1614782639336_trans_views2.sql
Normal file
|
@ -0,0 +1,33 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
-- This adds the isChild/parent_id constraint in `where`
|
||||
DROP VIEW IF EXISTS v_transactions_layer2;
|
||||
CREATE VIEW v_transactions_layer2 AS
|
||||
SELECT
|
||||
t.id AS id,
|
||||
t.isParent AS is_parent,
|
||||
t.isChild AS is_child,
|
||||
t.acct AS account,
|
||||
CASE WHEN t.isChild = 0 THEN NULL ELSE t.parent_id END AS parent_id,
|
||||
CASE WHEN t.isParent = 1 THEN NULL ELSE cm.transferId END AS category,
|
||||
pm.targetId AS payee,
|
||||
t.imported_description AS imported_payee,
|
||||
IFNULL(t.amount, 0) AS amount,
|
||||
t.notes AS notes,
|
||||
t.date AS date,
|
||||
t.financial_id AS imported_id,
|
||||
t.error AS error,
|
||||
t.starting_balance_flag AS starting_balance_flag,
|
||||
t.transferred_id AS transfer_id,
|
||||
t.sort_order AS sort_order,
|
||||
t.cleared AS cleared,
|
||||
t.tombstone AS tombstone
|
||||
FROM transactions t
|
||||
LEFT JOIN category_mapping cm ON cm.id = t.category
|
||||
LEFT JOIN payee_mapping pm ON pm.id = t.description
|
||||
WHERE
|
||||
t.date IS NOT NULL AND
|
||||
t.acct IS NOT NULL AND
|
||||
(t.isChild = 0 OR t.parent_id IS NOT NULL);
|
||||
|
||||
COMMIT;
|
10
packages/api/migrations/1615745967948_meta.sql
Normal file
10
packages/api/migrations/1615745967948_meta.sql
Normal file
|
@ -0,0 +1,10 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE __meta__ (key TEXT PRIMARY KEY, value TEXT);
|
||||
|
||||
DROP VIEW IF EXISTS v_transactions_layer2;
|
||||
DROP VIEW IF EXISTS v_transactions_layer1;
|
||||
DROP VIEW IF EXISTS v_transactions;
|
||||
DROP VIEW IF EXISTS v_categories;
|
||||
|
||||
COMMIT;
|
5
packages/api/migrations/1616167010796_accounts_order.sql
Normal file
5
packages/api/migrations/1616167010796_accounts_order.sql
Normal file
|
@ -0,0 +1,5 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE accounts ADD COLUMN sort_order REAL;
|
||||
|
||||
COMMIT;
|
28
packages/api/migrations/1618975177358_schedules.sql
Normal file
28
packages/api/migrations/1618975177358_schedules.sql
Normal file
|
@ -0,0 +1,28 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE schedules
|
||||
(id TEXT PRIMARY KEY,
|
||||
rule TEXT,
|
||||
active INTEGER DEFAULT 0,
|
||||
completed INTEGER DEFAULT 0,
|
||||
posts_transaction INTEGER DEFAULT 0,
|
||||
tombstone INTEGER DEFAULT 0);
|
||||
|
||||
CREATE TABLE schedules_next_date
|
||||
(id TEXT PRIMARY KEY,
|
||||
schedule_id TEXT,
|
||||
local_next_date INTEGER,
|
||||
local_next_date_ts INTEGER,
|
||||
base_next_date INTEGER,
|
||||
base_next_date_ts INTEGER);
|
||||
|
||||
CREATE TABLE schedules_json_paths
|
||||
(schedule_id TEXT PRIMARY KEY,
|
||||
payee TEXT,
|
||||
account TEXT,
|
||||
amount TEXT,
|
||||
date TEXT);
|
||||
|
||||
ALTER TABLE transactions ADD COLUMN schedule TEXT;
|
||||
|
||||
COMMIT;
|
135
packages/api/migrations/1632571489012_remove_cache.js
Normal file
135
packages/api/migrations/1632571489012_remove_cache.js
Normal file
|
@ -0,0 +1,135 @@
|
|||
export default async function runMigration(db, uuid) {
|
||||
function getValue(node) {
|
||||
return node.expr != null ? node.expr : node.cachedValue;
|
||||
}
|
||||
|
||||
db.execQuery(`
|
||||
CREATE TABLE zero_budget_months
|
||||
(id TEXT PRIMARY KEY,
|
||||
buffered INTEGER DEFAULT 0);
|
||||
|
||||
CREATE TABLE zero_budgets
|
||||
(id TEXT PRIMARY KEY,
|
||||
month INTEGER,
|
||||
category TEXT,
|
||||
amount INTEGER DEFAULT 0,
|
||||
carryover INTEGER DEFAULT 0);
|
||||
|
||||
CREATE TABLE reflect_budgets
|
||||
(id TEXT PRIMARY KEY,
|
||||
month INTEGER,
|
||||
category TEXT,
|
||||
amount INTEGER DEFAULT 0,
|
||||
carryover INTEGER DEFAULT 0);
|
||||
|
||||
CREATE TABLE notes
|
||||
(id TEXT PRIMARY KEY,
|
||||
note TEXT);
|
||||
|
||||
CREATE TABLE kvcache (key TEXT PRIMARY KEY, value TEXT);
|
||||
CREATE TABLE kvcache_key (id INTEGER PRIMARY KEY, key REAL);
|
||||
`);
|
||||
|
||||
// Migrate budget amounts and carryover
|
||||
let budget = db.runQuery(
|
||||
`SELECT * FROM spreadsheet_cells WHERE name LIKE 'budget%!budget-%'`,
|
||||
[],
|
||||
true
|
||||
);
|
||||
db.transaction(() => {
|
||||
budget.map(monthBudget => {
|
||||
let match = monthBudget.name.match(
|
||||
/^(budget-report|budget)(\d+)!budget-(.+)$/
|
||||
);
|
||||
if (match == null) {
|
||||
console.log('Warning: invalid budget month name', monthBudget.name);
|
||||
return;
|
||||
}
|
||||
|
||||
let type = match[1];
|
||||
let month = match[2].slice(0, 4) + '-' + match[2].slice(4);
|
||||
let dbmonth = parseInt(match[2]);
|
||||
let cat = match[3];
|
||||
|
||||
let amount = parseInt(getValue(monthBudget));
|
||||
if (isNaN(amount)) {
|
||||
amount = 0;
|
||||
}
|
||||
|
||||
let sheetName = monthBudget.name.split('!')[0];
|
||||
let carryover = db.runQuery(
|
||||
'SELECT * FROM spreadsheet_cells WHERE name = ?',
|
||||
[`${sheetName}!carryover-${cat}`],
|
||||
true
|
||||
);
|
||||
|
||||
let table = type === 'budget-report' ? 'reflect_budgets' : 'zero_budgets';
|
||||
db.runQuery(
|
||||
`INSERT INTO ${table} (id, month, category, amount, carryover) VALUES (?, ?, ?, ?, ?)`,
|
||||
[
|
||||
`${month}-${cat}`,
|
||||
dbmonth,
|
||||
cat,
|
||||
amount,
|
||||
carryover.length > 0 && getValue(carryover[0]) === 'true' ? 1 : 0
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Migrate buffers
|
||||
let buffers = db.runQuery(
|
||||
`SELECT * FROM spreadsheet_cells WHERE name LIKE 'budget%!buffered'`,
|
||||
[],
|
||||
true
|
||||
);
|
||||
db.transaction(() => {
|
||||
buffers.map(buffer => {
|
||||
let match = buffer.name.match(/^budget(\d+)!buffered$/);
|
||||
if (match) {
|
||||
let month = match[1].slice(0, 4) + '-' + match[1].slice(4);
|
||||
let amount = parseInt(getValue(buffer));
|
||||
if (isNaN(amount)) {
|
||||
amount = 0;
|
||||
}
|
||||
|
||||
db.runQuery(
|
||||
`INSERT INTO zero_budget_months (id, buffered) VALUES (?, ?)`,
|
||||
[month, amount]
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Migrate notes
|
||||
let notes = db.runQuery(
|
||||
`SELECT * FROM spreadsheet_cells WHERE name LIKE 'notes!%'`,
|
||||
[],
|
||||
true
|
||||
);
|
||||
|
||||
let parseNote = str => {
|
||||
try {
|
||||
let value = JSON.parse(str);
|
||||
return value && value !== '' ? value : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
db.transaction(() => {
|
||||
notes.forEach(note => {
|
||||
let parsed = parseNote(getValue(note));
|
||||
if (parsed) {
|
||||
let [, id] = note.name.split('!');
|
||||
db.runQuery(`INSERT INTO notes (id, note) VALUES (?, ?)`, [id, parsed]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
db.execQuery(`
|
||||
DROP TABLE spreadsheet_cells;
|
||||
ANALYZE;
|
||||
VACUUM;
|
||||
`);
|
||||
}
|
12
packages/api/package.json
Normal file
12
packages/api/package.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "@actual-app/api",
|
||||
"version": "4.0.1",
|
||||
"description": "An API for Actual",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^7.5.0",
|
||||
"uuid": "3.3.2",
|
||||
"node-fetch": "^1.6.3"
|
||||
}
|
||||
}
|
8
packages/api/test.js
Normal file
8
packages/api/test.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
let api = require('./index');
|
||||
|
||||
async function run() {
|
||||
let app = await api.init({ config: { dataDir: '/tmp' } });
|
||||
await app.send('create-budget', { testMode: true });
|
||||
}
|
||||
|
||||
run();
|
9
packages/api/utils.js
Normal file
9
packages/api/utils.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
function amountToInteger(n) {
|
||||
return Math.round(n * 100) | 0;
|
||||
}
|
||||
|
||||
function integerToAmount(n) {
|
||||
return parseFloat((n / 100).toFixed(2));
|
||||
}
|
||||
|
||||
module.exports = { amountToInteger, integerToAmount };
|
BIN
packages/browser/build/Inter/Inter-Black.woff2
Normal file
BIN
packages/browser/build/Inter/Inter-Black.woff2
Normal file
Binary file not shown.
BIN
packages/browser/build/Inter/Inter-BlackItalic.woff2
Normal file
BIN
packages/browser/build/Inter/Inter-BlackItalic.woff2
Normal file
Binary file not shown.
BIN
packages/browser/build/Inter/Inter-Bold.woff2
Normal file
BIN
packages/browser/build/Inter/Inter-Bold.woff2
Normal file
Binary file not shown.
BIN
packages/browser/build/Inter/Inter-BoldItalic.woff2
Normal file
BIN
packages/browser/build/Inter/Inter-BoldItalic.woff2
Normal file
Binary file not shown.
BIN
packages/browser/build/Inter/Inter-ExtraBold.woff2
Normal file
BIN
packages/browser/build/Inter/Inter-ExtraBold.woff2
Normal file
Binary file not shown.
BIN
packages/browser/build/Inter/Inter-ExtraBoldItalic.woff2
Normal file
BIN
packages/browser/build/Inter/Inter-ExtraBoldItalic.woff2
Normal file
Binary file not shown.
BIN
packages/browser/build/Inter/Inter-ExtraLight.woff2
Normal file
BIN
packages/browser/build/Inter/Inter-ExtraLight.woff2
Normal file
Binary file not shown.
BIN
packages/browser/build/Inter/Inter-ExtraLightItalic.woff2
Normal file
BIN
packages/browser/build/Inter/Inter-ExtraLightItalic.woff2
Normal file
Binary file not shown.
BIN
packages/browser/build/Inter/Inter-Italic.woff2
Normal file
BIN
packages/browser/build/Inter/Inter-Italic.woff2
Normal file
Binary file not shown.
BIN
packages/browser/build/Inter/Inter-Light.woff2
Normal file
BIN
packages/browser/build/Inter/Inter-Light.woff2
Normal file
Binary file not shown.
BIN
packages/browser/build/Inter/Inter-LightItalic.woff2
Normal file
BIN
packages/browser/build/Inter/Inter-LightItalic.woff2
Normal file
Binary file not shown.
BIN
packages/browser/build/Inter/Inter-Medium.woff2
Normal file
BIN
packages/browser/build/Inter/Inter-Medium.woff2
Normal file
Binary file not shown.
BIN
packages/browser/build/Inter/Inter-MediumItalic.woff2
Normal file
BIN
packages/browser/build/Inter/Inter-MediumItalic.woff2
Normal file
Binary file not shown.
BIN
packages/browser/build/Inter/Inter-Regular.woff2
Normal file
BIN
packages/browser/build/Inter/Inter-Regular.woff2
Normal file
Binary file not shown.
BIN
packages/browser/build/Inter/Inter-SemiBold.woff2
Normal file
BIN
packages/browser/build/Inter/Inter-SemiBold.woff2
Normal file
Binary file not shown.
BIN
packages/browser/build/Inter/Inter-SemiBoldItalic.woff2
Normal file
BIN
packages/browser/build/Inter/Inter-SemiBoldItalic.woff2
Normal file
Binary file not shown.
BIN
packages/browser/build/Inter/Inter-Thin.woff2
Normal file
BIN
packages/browser/build/Inter/Inter-Thin.woff2
Normal file
Binary file not shown.
BIN
packages/browser/build/Inter/Inter-ThinItalic.woff2
Normal file
BIN
packages/browser/build/Inter/Inter-ThinItalic.woff2
Normal file
Binary file not shown.
178
packages/browser/build/Inter/inter.css
Normal file
178
packages/browser/build/Inter/inter.css
Normal file
|
@ -0,0 +1,178 @@
|
|||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("Inter-Thin.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-Thin.woff?v=3.12") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
font-display: swap;
|
||||
src: url("Inter-ThinItalic.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-ThinItalic.woff?v=3.12") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraLight.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-ExtraLight.woff?v=3.12") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraLightItalic.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-ExtraLightItalic.woff?v=3.12") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("Inter-Light.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-Light.woff?v=3.12") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: url("Inter-LightItalic.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-LightItalic.woff?v=3.12") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("Inter-Regular.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-Regular.woff?v=3.12") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("Inter-Italic.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-Italic.woff?v=3.12") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("Inter-Medium.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-Medium.woff?v=3.12") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("Inter-MediumItalic.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-MediumItalic.woff?v=3.12") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("Inter-SemiBold.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-SemiBold.woff?v=3.12") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("Inter-SemiBoldItalic.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-SemiBoldItalic.woff?v=3.12") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("Inter-Bold.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-Bold.woff?v=3.12") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("Inter-BoldItalic.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-BoldItalic.woff?v=3.12") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraBold.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-ExtraBold.woff?v=3.12") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
font-display: swap;
|
||||
src: url("Inter-ExtraBoldItalic.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-ExtraBoldItalic.woff?v=3.12") format("woff");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("Inter-Black.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-Black.woff?v=3.12") format("woff");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
font-display: swap;
|
||||
src: url("Inter-BlackItalic.woff2?v=3.12") format("woff2"),
|
||||
url("Inter-BlackItalic.woff?v=3.12") format("woff");
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------
|
||||
Variable font.
|
||||
Usage:
|
||||
|
||||
html { font-family: 'Inter', sans-serif; }
|
||||
@supports (font-variation-settings: normal) {
|
||||
html { font-family: 'Inter var', sans-serif; }
|
||||
}
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter var';
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
font-named-instance: 'Regular';
|
||||
src: url("Inter-roman.var.woff2?v=3.12") format("woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter var';
|
||||
font-weight: 100 900;
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
font-named-instance: 'Italic';
|
||||
src: url("Inter-italic.var.woff2?v=3.12") format("woff2");
|
||||
}
|
10
packages/browser/build/_headers
Normal file
10
packages/browser/build/_headers
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
Cross-Origin-Opener-Policy: same-origin
|
||||
Cross-Origin-Embedder-Policy: require-corp
|
||||
Content-Security-Policy: default-src 'self' https://sentry.io blob:; script-src 'self' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'; connect-src 'self' https://*.actualbudget.com https://api.mixpanel.com https://sentry.io;
|
||||
|
||||
/kcab/*
|
||||
Content-Security-Policy: default-src 'self' https://sentry.io blob:; script-src 'self' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'; connect-src 'self' https://*.actualbudget.com https://api.mixpanel.com https://sentry.io;
|
||||
|
||||
/*.wasm
|
||||
Content-Type: application/wasm
|
1
packages/browser/build/_redirects
Normal file
1
packages/browser/build/_redirects
Normal file
|
@ -0,0 +1 @@
|
|||
/* /index.html 200
|
BIN
packages/browser/build/android-chrome-192x192.png
Normal file
BIN
packages/browser/build/android-chrome-192x192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
BIN
packages/browser/build/android-chrome-512x512.png
Normal file
BIN
packages/browser/build/android-chrome-512x512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.4 KiB |
BIN
packages/browser/build/apple-touch-icon.png
Normal file
BIN
packages/browser/build/apple-touch-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.7 KiB |
22
packages/browser/build/asset-manifest.json
Normal file
22
packages/browser/build/asset-manifest.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"main.js": "/static/js/main.a27b6a9d.chunk.js",
|
||||
"main.js.map": "/static/js/main.a27b6a9d.chunk.js.map",
|
||||
"static/js/1.5b0e59dd.chunk.js": "/static/js/1.5b0e59dd.chunk.js",
|
||||
"static/js/1.5b0e59dd.chunk.js.map": "/static/js/1.5b0e59dd.chunk.js.map",
|
||||
"static/js/2.c0b1e834.chunk.js": "/static/js/2.c0b1e834.chunk.js",
|
||||
"static/js/2.c0b1e834.chunk.js.map": "/static/js/2.c0b1e834.chunk.js.map",
|
||||
"static/css/3.d37e3633.chunk.css": "/static/css/3.d37e3633.chunk.css",
|
||||
"static/js/3.03227275.chunk.js": "/static/js/3.03227275.chunk.js",
|
||||
"static/js/3.03227275.chunk.js.map": "/static/js/3.03227275.chunk.js.map",
|
||||
"runtime~main.js": "/static/js/runtime~main.d358c073.js",
|
||||
"runtime~main.js.map": "/static/js/runtime~main.d358c073.js.map",
|
||||
"static/js/browser-server.06b54be5.worker.js": "/static/js/browser-server.06b54be5.worker.js",
|
||||
"static/js/browser-server.06b54be5.worker.js.map": "/static/js/browser-server.06b54be5.worker.js.map",
|
||||
"static/media/DateSelect.left.png": "/static/media/DateSelect.left.1ae92674.png",
|
||||
"static/media/DateSelect.right.png": "/static/media/DateSelect.right.2a470dc0.png",
|
||||
"static/media/bg.svg": "/static/media/bg.ad9dce3b.svg",
|
||||
"static/css/3.d37e3633.chunk.css.map": "/static/css/3.d37e3633.chunk.css.map",
|
||||
"index.html": "/index.html",
|
||||
"precache-manifest.dd195c6a2e68b333b66a2a33a8dce9a0.js": "/precache-manifest.dd195c6a2e68b333b66a2a33a8dce9a0.js",
|
||||
"service-worker.js": "/service-worker.js"
|
||||
}
|
9
packages/browser/build/browserconfig.xml
Normal file
9
packages/browser/build/browserconfig.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<TileColor>#da532c</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
341
packages/browser/build/codemirror.css
Normal file
341
packages/browser/build/codemirror.css
Normal file
|
@ -0,0 +1,341 @@
|
|||
/* BASICS */
|
||||
|
||||
.CodeMirror {
|
||||
/* Set height, width, borders, and global font properties here */
|
||||
font-family: monospace;
|
||||
height: 300px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
/* PADDING */
|
||||
|
||||
.CodeMirror-lines {
|
||||
padding: 4px 0; /* Vertical padding around content */
|
||||
}
|
||||
.CodeMirror pre {
|
||||
padding: 0 4px; /* Horizontal padding of content */
|
||||
}
|
||||
|
||||
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
background-color: white; /* The little square between H and V scrollbars */
|
||||
}
|
||||
|
||||
/* GUTTER */
|
||||
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid #ddd;
|
||||
background-color: #f7f7f7;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.CodeMirror-linenumbers {}
|
||||
.CodeMirror-linenumber {
|
||||
padding: 0 3px 0 5px;
|
||||
min-width: 20px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.CodeMirror-guttermarker { color: black; }
|
||||
.CodeMirror-guttermarker-subtle { color: #999; }
|
||||
|
||||
/* CURSOR */
|
||||
|
||||
.CodeMirror-cursor {
|
||||
border-left: 1px solid black;
|
||||
border-right: none;
|
||||
width: 0;
|
||||
}
|
||||
/* Shown when moving in bi-directional text */
|
||||
.CodeMirror div.CodeMirror-secondarycursor {
|
||||
border-left: 1px solid silver;
|
||||
}
|
||||
.cm-fat-cursor .CodeMirror-cursor {
|
||||
width: auto;
|
||||
border: 0 !important;
|
||||
background: #7e7;
|
||||
}
|
||||
.cm-fat-cursor div.CodeMirror-cursors {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.cm-animate-fat-cursor {
|
||||
width: auto;
|
||||
border: 0;
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
background-color: #7e7;
|
||||
}
|
||||
@-moz-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@-webkit-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
@keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
}
|
||||
|
||||
/* Can style cursor different in overwrite (non-insert) mode */
|
||||
.CodeMirror-overwrite .CodeMirror-cursor {}
|
||||
|
||||
.cm-tab { display: inline-block; text-decoration: inherit; }
|
||||
|
||||
.CodeMirror-rulers {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: -50px; bottom: -20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.CodeMirror-ruler {
|
||||
border-left: 1px solid #ccc;
|
||||
top: 0; bottom: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* DEFAULT THEME */
|
||||
|
||||
.cm-s-default .cm-header {color: blue;}
|
||||
.cm-s-default .cm-quote {color: #090;}
|
||||
.cm-negative {color: #d44;}
|
||||
.cm-positive {color: #292;}
|
||||
.cm-header, .cm-strong {font-weight: bold;}
|
||||
.cm-em {font-style: italic;}
|
||||
.cm-link {text-decoration: underline;}
|
||||
.cm-strikethrough {text-decoration: line-through;}
|
||||
|
||||
.cm-s-default .cm-keyword {color: #708;}
|
||||
.cm-s-default .cm-atom {color: #219;}
|
||||
.cm-s-default .cm-number {color: #164;}
|
||||
.cm-s-default .cm-def {color: #00f;}
|
||||
.cm-s-default .cm-variable,
|
||||
.cm-s-default .cm-punctuation,
|
||||
.cm-s-default .cm-property,
|
||||
.cm-s-default .cm-operator {}
|
||||
.cm-s-default .cm-variable-2 {color: #05a;}
|
||||
.cm-s-default .cm-variable-3 {color: #085;}
|
||||
.cm-s-default .cm-comment {color: #a50;}
|
||||
.cm-s-default .cm-string {color: #a11;}
|
||||
.cm-s-default .cm-string-2 {color: #f50;}
|
||||
.cm-s-default .cm-meta {color: #555;}
|
||||
.cm-s-default .cm-qualifier {color: #555;}
|
||||
.cm-s-default .cm-builtin {color: #30a;}
|
||||
.cm-s-default .cm-bracket {color: #997;}
|
||||
.cm-s-default .cm-tag {color: #170;}
|
||||
.cm-s-default .cm-attribute {color: #00c;}
|
||||
.cm-s-default .cm-hr {color: #999;}
|
||||
.cm-s-default .cm-link {color: #00c;}
|
||||
|
||||
.cm-s-default .cm-error {color: #f00;}
|
||||
.cm-invalidchar {color: #f00;}
|
||||
|
||||
.CodeMirror-composing { border-bottom: 2px solid; }
|
||||
|
||||
/* Default styles for common addons */
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
|
||||
.CodeMirror-activeline-background {background: #e8f2ff;}
|
||||
|
||||
/* STOP */
|
||||
|
||||
/* The rest of this file contains styles related to the mechanics of
|
||||
the editor. You probably shouldn't touch them. */
|
||||
|
||||
.CodeMirror {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
overflow: scroll !important; /* Things will break if this is overridden */
|
||||
/* 30px is the magic margin used to hide the element's real scrollbars */
|
||||
/* See overflow: hidden in .CodeMirror */
|
||||
margin-bottom: -30px; margin-right: -30px;
|
||||
padding-bottom: 30px;
|
||||
height: 100%;
|
||||
outline: none; /* Prevent dragging from highlighting the element */
|
||||
position: relative;
|
||||
}
|
||||
.CodeMirror-sizer {
|
||||
position: relative;
|
||||
border-right: 30px solid transparent;
|
||||
}
|
||||
|
||||
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||
before actual scrolling happens, thus preventing shaking and
|
||||
flickering artifacts. */
|
||||
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
display: none;
|
||||
}
|
||||
.CodeMirror-vscrollbar {
|
||||
right: 0; top: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.CodeMirror-hscrollbar {
|
||||
bottom: 0; left: 0;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
.CodeMirror-scrollbar-filler {
|
||||
right: 0; bottom: 0;
|
||||
}
|
||||
.CodeMirror-gutter-filler {
|
||||
left: 0; bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
position: absolute; left: 0; top: 0;
|
||||
min-height: 100%;
|
||||
z-index: 3;
|
||||
}
|
||||
.CodeMirror-gutter {
|
||||
white-space: normal;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-bottom: -30px;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
.CodeMirror-gutter-background {
|
||||
position: absolute;
|
||||
top: 0; bottom: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-elt {
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.CodeMirror-lines {
|
||||
cursor: text;
|
||||
min-height: 1px; /* prevents collapsing before first draw */
|
||||
}
|
||||
.CodeMirror pre {
|
||||
/* Reset some styles that the rest of the page might have set */
|
||||
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||
border-width: 0;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-font-variant-ligatures: contextual;
|
||||
font-variant-ligatures: contextual;
|
||||
}
|
||||
.CodeMirror-wrap pre {
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.CodeMirror-linebackground {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-linewidget {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.CodeMirror-widget {}
|
||||
|
||||
.CodeMirror-code {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Force content-box sizing for the elements where we expect it */
|
||||
.CodeMirror-scroll,
|
||||
.CodeMirror-sizer,
|
||||
.CodeMirror-gutter,
|
||||
.CodeMirror-gutters,
|
||||
.CodeMirror-linenumber {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.CodeMirror-measure {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.CodeMirror-cursor {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.CodeMirror-measure pre { position: static; }
|
||||
|
||||
div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
div.CodeMirror-dragcursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-focused div.CodeMirror-cursors {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-selected { background: #d9d9d9; }
|
||||
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||
.CodeMirror-crosshair { cursor: crosshair; }
|
||||
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
||||
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
||||
|
||||
.cm-searching {
|
||||
background: #ffa;
|
||||
background: rgba(255, 255, 0, .4);
|
||||
}
|
||||
|
||||
/* Used to force a border model for a node */
|
||||
.cm-force-border { padding-right: .1px; }
|
||||
|
||||
@media print {
|
||||
/* Hide the cursor when printing */
|
||||
.CodeMirror div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* See issue #2901 */
|
||||
.cm-tab-wrap-hack:after { content: ''; }
|
||||
|
||||
/* Help users use markselection to safely style text background */
|
||||
span.CodeMirror-selectedtext { background: none; }
|
17
packages/browser/build/data-file-index.txt
Normal file
17
packages/browser/build/data-file-index.txt
Normal file
|
@ -0,0 +1,17 @@
|
|||
default-db.sqlite
|
||||
migrations/1632571489012_remove_cache.js
|
||||
migrations/1615745967948_meta.sql
|
||||
migrations/1555786194328_remove_category_group_unique.sql
|
||||
migrations/1567699552727_budget.sql
|
||||
migrations/1608652596044_trans_views.sql
|
||||
migrations/1548957970627_remove-db-version.sql
|
||||
migrations/1561751833510_indexes.sql
|
||||
migrations/1550601598648_payees.sql
|
||||
migrations/1582384163573_cleared.sql
|
||||
migrations/1618975177358_schedules.sql
|
||||
migrations/.force-copy-windows
|
||||
migrations/1597756566448_rules.sql
|
||||
migrations/1616167010796_accounts_order.sql
|
||||
migrations/1608652596043_parent_field.sql
|
||||
migrations/1612625548236_optimize.sql
|
||||
migrations/1614782639336_trans_views2.sql
|
BIN
packages/browser/build/data/default-db.sqlite
Normal file
BIN
packages/browser/build/data/default-db.sqlite
Normal file
Binary file not shown.
|
@ -0,0 +1,5 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
DROP TABLE db_version;
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,23 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE payees
|
||||
(id TEXT PRIMARY KEY,
|
||||
name TEXT,
|
||||
category TEXT,
|
||||
tombstone INTEGER DEFAULT 0,
|
||||
transfer_acct TEXT);
|
||||
|
||||
CREATE TABLE payee_rules
|
||||
(id TEXT PRIMARY KEY,
|
||||
payee_id TEXT,
|
||||
type TEXT,
|
||||
value TEXT,
|
||||
tombstone INTEGER DEFAULT 0);
|
||||
|
||||
CREATE INDEX payee_rules_lowercase_index ON payee_rules(LOWER(value));
|
||||
|
||||
CREATE TABLE payee_mapping
|
||||
(id TEXT PRIMARY KEY,
|
||||
targetId TEXT);
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,25 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TEMPORARY TABLE category_groups_tmp
|
||||
(id TEXT PRIMARY KEY,
|
||||
name TEXT UNIQUE,
|
||||
is_income INTEGER DEFAULT 0,
|
||||
sort_order REAL,
|
||||
tombstone INTEGER DEFAULT 0);
|
||||
|
||||
INSERT INTO category_groups_tmp SELECT * FROM category_groups;
|
||||
|
||||
DROP TABLE category_groups;
|
||||
|
||||
CREATE TABLE category_groups
|
||||
(id TEXT PRIMARY KEY,
|
||||
name TEXT,
|
||||
is_income INTEGER DEFAULT 0,
|
||||
sort_order REAL,
|
||||
tombstone INTEGER DEFAULT 0);
|
||||
|
||||
INSERT INTO category_groups SELECT * FROM category_groups_tmp;
|
||||
|
||||
DROP TABLE category_groups_tmp;
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,7 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE INDEX trans_category_date ON transactions(category, date);
|
||||
CREATE INDEX trans_category ON transactions(category);
|
||||
CREATE INDEX trans_date ON transactions(date);
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,38 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
DELETE FROM spreadsheet_cells WHERE
|
||||
name NOT LIKE '%!budget\_%' ESCAPE '\' AND
|
||||
name NOT LIKE '%!carryover\_%' ESCAPE '\' AND
|
||||
name NOT LIKE '%!buffered';
|
||||
|
||||
UPDATE OR REPLACE spreadsheet_cells SET name = REPLACE(name, '_', '-');
|
||||
|
||||
UPDATE OR REPLACE spreadsheet_cells SET
|
||||
name =
|
||||
SUBSTR(name, 1, 28) ||
|
||||
'-' ||
|
||||
SUBSTR(name, 29, 4) ||
|
||||
'-' ||
|
||||
SUBSTR(name, 33, 4) ||
|
||||
'-' ||
|
||||
SUBSTR(name, 37, 4) ||
|
||||
'-' ||
|
||||
SUBSTR(name, 41, 12)
|
||||
WHERE name LIKE '%!budget-%' AND LENGTH(name) = 52;
|
||||
|
||||
UPDATE OR REPLACE spreadsheet_cells SET
|
||||
name =
|
||||
SUBSTR(name, 1, 31) ||
|
||||
'-' ||
|
||||
SUBSTR(name, 32, 4) ||
|
||||
'-' ||
|
||||
SUBSTR(name, 36, 4) ||
|
||||
'-' ||
|
||||
SUBSTR(name, 40, 4) ||
|
||||
'-' ||
|
||||
SUBSTR(name, 44, 12)
|
||||
WHERE name LIKE '%!carryover-%' AND LENGTH(name) = 55;
|
||||
|
||||
UPDATE spreadsheet_cells SET expr = SUBSTR(expr, 2) WHERE name LIKE '%!carryover-%';
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,6 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE transactions ADD COLUMN cleared INTEGER DEFAULT 1;
|
||||
ALTER TABLE transactions ADD COLUMN pending INTEGER DEFAULT 0;
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,10 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE rules
|
||||
(id TEXT PRIMARY KEY,
|
||||
stage TEXT,
|
||||
conditions TEXT,
|
||||
actions TEXT,
|
||||
tombstone INTEGER DEFAULT 0);
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,13 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE transactions ADD COLUMN parent_id TEXT;
|
||||
|
||||
UPDATE transactions SET
|
||||
parent_id = CASE
|
||||
WHEN isChild THEN SUBSTR(id, 1, INSTR(id, '/') - 1)
|
||||
ELSE NULL
|
||||
END;
|
||||
|
||||
CREATE INDEX trans_parent_id ON transactions(parent_id);
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,56 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
DROP VIEW IF EXISTS v_transactions_layer2;
|
||||
CREATE VIEW v_transactions_layer2 AS
|
||||
SELECT
|
||||
t.id AS id,
|
||||
t.isParent AS is_parent,
|
||||
t.isChild AS is_child,
|
||||
t.acct AS account,
|
||||
CASE WHEN t.isChild = 0 THEN NULL ELSE t.parent_id END AS parent_id,
|
||||
CASE WHEN t.isParent = 1 THEN NULL ELSE cm.transferId END AS category,
|
||||
pm.targetId AS payee,
|
||||
t.imported_description AS imported_payee,
|
||||
IFNULL(t.amount, 0) AS amount,
|
||||
t.notes AS notes,
|
||||
t.date AS date,
|
||||
t.financial_id AS imported_id,
|
||||
t.error AS error,
|
||||
t.starting_balance_flag AS starting_balance_flag,
|
||||
t.transferred_id AS transfer_id,
|
||||
t.sort_order AS sort_order,
|
||||
t.cleared AS cleared,
|
||||
t.tombstone AS tombstone
|
||||
FROM transactions t
|
||||
LEFT JOIN category_mapping cm ON cm.id = t.category
|
||||
LEFT JOIN payee_mapping pm ON pm.id = t.description
|
||||
WHERE
|
||||
t.date IS NOT NULL AND
|
||||
t.acct IS NOT NULL;
|
||||
|
||||
CREATE INDEX trans_sorted ON transactions(date desc, starting_balance_flag, sort_order desc, id);
|
||||
|
||||
DROP VIEW IF EXISTS v_transactions_layer1;
|
||||
CREATE VIEW v_transactions_layer1 AS
|
||||
SELECT t.* FROM v_transactions_layer2 t
|
||||
LEFT JOIN transactions t2 ON (t.is_child = 1 AND t2.id = t.parent_id)
|
||||
WHERE IFNULL(t.tombstone, 0) = 0 AND IFNULL(t2.tombstone, 0) = 0;
|
||||
|
||||
DROP VIEW IF EXISTS v_transactions;
|
||||
CREATE VIEW v_transactions AS
|
||||
SELECT t.* FROM v_transactions_layer1 t
|
||||
ORDER BY t.date desc, t.starting_balance_flag, t.sort_order desc, t.id;
|
||||
|
||||
|
||||
DROP VIEW IF EXISTS v_categories;
|
||||
CREATE VIEW v_categories AS
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
is_income,
|
||||
cat_group AS "group",
|
||||
sort_order,
|
||||
tombstone
|
||||
FROM categories;
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,7 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE INDEX messages_crdt_search ON messages_crdt(dataset, row, column, timestamp);
|
||||
|
||||
ANALYZE;
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,33 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
-- This adds the isChild/parent_id constraint in `where`
|
||||
DROP VIEW IF EXISTS v_transactions_layer2;
|
||||
CREATE VIEW v_transactions_layer2 AS
|
||||
SELECT
|
||||
t.id AS id,
|
||||
t.isParent AS is_parent,
|
||||
t.isChild AS is_child,
|
||||
t.acct AS account,
|
||||
CASE WHEN t.isChild = 0 THEN NULL ELSE t.parent_id END AS parent_id,
|
||||
CASE WHEN t.isParent = 1 THEN NULL ELSE cm.transferId END AS category,
|
||||
pm.targetId AS payee,
|
||||
t.imported_description AS imported_payee,
|
||||
IFNULL(t.amount, 0) AS amount,
|
||||
t.notes AS notes,
|
||||
t.date AS date,
|
||||
t.financial_id AS imported_id,
|
||||
t.error AS error,
|
||||
t.starting_balance_flag AS starting_balance_flag,
|
||||
t.transferred_id AS transfer_id,
|
||||
t.sort_order AS sort_order,
|
||||
t.cleared AS cleared,
|
||||
t.tombstone AS tombstone
|
||||
FROM transactions t
|
||||
LEFT JOIN category_mapping cm ON cm.id = t.category
|
||||
LEFT JOIN payee_mapping pm ON pm.id = t.description
|
||||
WHERE
|
||||
t.date IS NOT NULL AND
|
||||
t.acct IS NOT NULL AND
|
||||
(t.isChild = 0 OR t.parent_id IS NOT NULL);
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,10 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE __meta__ (key TEXT PRIMARY KEY, value TEXT);
|
||||
|
||||
DROP VIEW IF EXISTS v_transactions_layer2;
|
||||
DROP VIEW IF EXISTS v_transactions_layer1;
|
||||
DROP VIEW IF EXISTS v_transactions;
|
||||
DROP VIEW IF EXISTS v_categories;
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,5 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE accounts ADD COLUMN sort_order REAL;
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,28 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
CREATE TABLE schedules
|
||||
(id TEXT PRIMARY KEY,
|
||||
rule TEXT,
|
||||
active INTEGER DEFAULT 0,
|
||||
completed INTEGER DEFAULT 0,
|
||||
posts_transaction INTEGER DEFAULT 0,
|
||||
tombstone INTEGER DEFAULT 0);
|
||||
|
||||
CREATE TABLE schedules_next_date
|
||||
(id TEXT PRIMARY KEY,
|
||||
schedule_id TEXT,
|
||||
local_next_date INTEGER,
|
||||
local_next_date_ts INTEGER,
|
||||
base_next_date INTEGER,
|
||||
base_next_date_ts INTEGER);
|
||||
|
||||
CREATE TABLE schedules_json_paths
|
||||
(schedule_id TEXT PRIMARY KEY,
|
||||
payee TEXT,
|
||||
account TEXT,
|
||||
amount TEXT,
|
||||
date TEXT);
|
||||
|
||||
ALTER TABLE transactions ADD COLUMN schedule TEXT;
|
||||
|
||||
COMMIT;
|
|
@ -0,0 +1,135 @@
|
|||
export default async function runMigration(db, uuid) {
|
||||
function getValue(node) {
|
||||
return node.expr != null ? node.expr : node.cachedValue;
|
||||
}
|
||||
|
||||
db.execQuery(`
|
||||
CREATE TABLE zero_budget_months
|
||||
(id TEXT PRIMARY KEY,
|
||||
buffered INTEGER DEFAULT 0);
|
||||
|
||||
CREATE TABLE zero_budgets
|
||||
(id TEXT PRIMARY KEY,
|
||||
month INTEGER,
|
||||
category TEXT,
|
||||
amount INTEGER DEFAULT 0,
|
||||
carryover INTEGER DEFAULT 0);
|
||||
|
||||
CREATE TABLE reflect_budgets
|
||||
(id TEXT PRIMARY KEY,
|
||||
month INTEGER,
|
||||
category TEXT,
|
||||
amount INTEGER DEFAULT 0,
|
||||
carryover INTEGER DEFAULT 0);
|
||||
|
||||
CREATE TABLE notes
|
||||
(id TEXT PRIMARY KEY,
|
||||
note TEXT);
|
||||
|
||||
CREATE TABLE kvcache (key TEXT PRIMARY KEY, value TEXT);
|
||||
CREATE TABLE kvcache_key (id INTEGER PRIMARY KEY, key REAL);
|
||||
`);
|
||||
|
||||
// Migrate budget amounts and carryover
|
||||
let budget = db.runQuery(
|
||||
`SELECT * FROM spreadsheet_cells WHERE name LIKE 'budget%!budget-%'`,
|
||||
[],
|
||||
true
|
||||
);
|
||||
db.transaction(() => {
|
||||
budget.map(monthBudget => {
|
||||
let match = monthBudget.name.match(
|
||||
/^(budget-report|budget)(\d+)!budget-(.+)$/
|
||||
);
|
||||
if (match == null) {
|
||||
console.log('Warning: invalid budget month name', monthBudget.name);
|
||||
return;
|
||||
}
|
||||
|
||||
let type = match[1];
|
||||
let month = match[2].slice(0, 4) + '-' + match[2].slice(4);
|
||||
let dbmonth = parseInt(match[2]);
|
||||
let cat = match[3];
|
||||
|
||||
let amount = parseInt(getValue(monthBudget));
|
||||
if (isNaN(amount)) {
|
||||
amount = 0;
|
||||
}
|
||||
|
||||
let sheetName = monthBudget.name.split('!')[0];
|
||||
let carryover = db.runQuery(
|
||||
'SELECT * FROM spreadsheet_cells WHERE name = ?',
|
||||
[`${sheetName}!carryover-${cat}`],
|
||||
true
|
||||
);
|
||||
|
||||
let table = type === 'budget-report' ? 'reflect_budgets' : 'zero_budgets';
|
||||
db.runQuery(
|
||||
`INSERT INTO ${table} (id, month, category, amount, carryover) VALUES (?, ?, ?, ?, ?)`,
|
||||
[
|
||||
`${month}-${cat}`,
|
||||
dbmonth,
|
||||
cat,
|
||||
amount,
|
||||
carryover.length > 0 && getValue(carryover[0]) === 'true' ? 1 : 0
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Migrate buffers
|
||||
let buffers = db.runQuery(
|
||||
`SELECT * FROM spreadsheet_cells WHERE name LIKE 'budget%!buffered'`,
|
||||
[],
|
||||
true
|
||||
);
|
||||
db.transaction(() => {
|
||||
buffers.map(buffer => {
|
||||
let match = buffer.name.match(/^budget(\d+)!buffered$/);
|
||||
if (match) {
|
||||
let month = match[1].slice(0, 4) + '-' + match[1].slice(4);
|
||||
let amount = parseInt(getValue(buffer));
|
||||
if (isNaN(amount)) {
|
||||
amount = 0;
|
||||
}
|
||||
|
||||
db.runQuery(
|
||||
`INSERT INTO zero_budget_months (id, buffered) VALUES (?, ?)`,
|
||||
[month, amount]
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Migrate notes
|
||||
let notes = db.runQuery(
|
||||
`SELECT * FROM spreadsheet_cells WHERE name LIKE 'notes!%'`,
|
||||
[],
|
||||
true
|
||||
);
|
||||
|
||||
let parseNote = str => {
|
||||
try {
|
||||
let value = JSON.parse(str);
|
||||
return value && value !== '' ? value : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
db.transaction(() => {
|
||||
notes.forEach(note => {
|
||||
let parsed = parseNote(getValue(note));
|
||||
if (parsed) {
|
||||
let [, id] = note.name.split('!');
|
||||
db.runQuery(`INSERT INTO notes (id, note) VALUES (?, ?)`, [id, parsed]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
db.execQuery(`
|
||||
DROP TABLE spreadsheet_cells;
|
||||
ANALYZE;
|
||||
VACUUM;
|
||||
`);
|
||||
}
|
BIN
packages/browser/build/favicon-16x16.png
Normal file
BIN
packages/browser/build/favicon-16x16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 978 B |
BIN
packages/browser/build/favicon-32x32.png
Normal file
BIN
packages/browser/build/favicon-32x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
packages/browser/build/favicon.ico
Normal file
BIN
packages/browser/build/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
1
packages/browser/build/index.html
Normal file
1
packages/browser/build/index.html
Normal file
|
@ -0,0 +1 @@
|
|||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>Actual</title><link rel="canonical" href="/"/><link rel="shortcut icon" href="/favicon.ico"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="manifest" href="/site.webmanifest"/><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5"/><meta name="msapplication-TileColor" content="#da532c"/><meta name="theme-color" content="#ffffff"/><link rel="stylesheet" href="/Inter/inter.css"/><style>body,html{margin:0;padding:0;font-size:13px;color:#272630}body,button,html,input{font-family:Inter,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Fira Sans','Droid Sans','Helvetica Neue',Helvetica,Arial,sans-serif}a{color:#272630;text-decoration-skip:ink}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:focus{outline-color:#2b8fed}input,textarea{font-size:1em;font-family:Inter,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Fira Sans','Droid Sans','Helvetica Neue',Helvetica,Arial,sans-serif}#root,body,html{height:100%}body{overflow:hidden}.view{align-items:stretch;border-width:0;border-style:solid;box-sizing:border-box;display:flex;flex-direction:column;margin:0;padding:0;position:relative;min-height:0;min-width:0}.js-focus-visible :focus:not(.focus-visible){outline:0}</style><link href="/static/css/3.d37e3633.chunk.css" rel="stylesheet"></head><body><div id="root"></div><script src="/static/js/runtime~main.d358c073.js"></script><script src="/static/js/3.03227275.chunk.js"></script><script src="/static/js/main.a27b6a9d.chunk.js"></script></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
packages/browser/build/libofx.web.wasm
Normal file
BIN
packages/browser/build/libofx.web.wasm
Normal file
Binary file not shown.
36
packages/browser/build/monokai.css
Normal file
36
packages/browser/build/monokai.css
Normal file
|
@ -0,0 +1,36 @@
|
|||
/* Based on Sublime Text's Monokai theme */
|
||||
|
||||
.cm-s-monokai.CodeMirror { background: #272822; color: #f8f8f2; }
|
||||
.cm-s-monokai div.CodeMirror-selected { background: #49483E; }
|
||||
.cm-s-monokai .CodeMirror-line::selection, .cm-s-monokai .CodeMirror-line > span::selection, .cm-s-monokai .CodeMirror-line > span > span::selection { background: rgba(73, 72, 62, .99); }
|
||||
.cm-s-monokai .CodeMirror-line::-moz-selection, .cm-s-monokai .CodeMirror-line > span::-moz-selection, .cm-s-monokai .CodeMirror-line > span > span::-moz-selection { background: rgba(73, 72, 62, .99); }
|
||||
.cm-s-monokai .CodeMirror-gutters { background: #272822; border-right: 0px; }
|
||||
.cm-s-monokai .CodeMirror-guttermarker { color: white; }
|
||||
.cm-s-monokai .CodeMirror-guttermarker-subtle { color: #d0d0d0; }
|
||||
.cm-s-monokai .CodeMirror-linenumber { color: #d0d0d0; }
|
||||
.cm-s-monokai .CodeMirror-cursor { border-left: 1px solid #f8f8f0; }
|
||||
|
||||
.cm-s-monokai span.cm-comment { color: #75715e; }
|
||||
.cm-s-monokai span.cm-atom { color: #ae81ff; }
|
||||
.cm-s-monokai span.cm-number { color: #ae81ff; }
|
||||
|
||||
.cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute { color: #a6e22e; }
|
||||
.cm-s-monokai span.cm-keyword { color: #f92672; }
|
||||
.cm-s-monokai span.cm-builtin { color: #66d9ef; }
|
||||
.cm-s-monokai span.cm-string { color: #e6db74; }
|
||||
|
||||
.cm-s-monokai span.cm-variable { color: #f8f8f2; }
|
||||
.cm-s-monokai span.cm-variable-2 { color: #9effff; }
|
||||
.cm-s-monokai span.cm-variable-3 { color: #66d9ef; }
|
||||
.cm-s-monokai span.cm-def { color: #fd971f; }
|
||||
.cm-s-monokai span.cm-bracket { color: #f8f8f2; }
|
||||
.cm-s-monokai span.cm-tag { color: #f92672; }
|
||||
.cm-s-monokai span.cm-header { color: #ae81ff; }
|
||||
.cm-s-monokai span.cm-link { color: #ae81ff; }
|
||||
.cm-s-monokai span.cm-error { background: #f92672; color: #f8f8f0; }
|
||||
|
||||
.cm-s-monokai .CodeMirror-activeline-background { background: #373831; }
|
||||
.cm-s-monokai .CodeMirror-matchingbracket {
|
||||
text-decoration: underline;
|
||||
color: white !important;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue