@accounter/scraper-app 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +90 -0
  2. package/docs/plan.md +76 -0
  3. package/index.html +12 -0
  4. package/package.json +40 -0
  5. package/src/env.template +2 -0
  6. package/src/server/__tests__/accounts-routes.test.ts +133 -0
  7. package/src/server/__tests__/check-accounts.test.ts +305 -0
  8. package/src/server/__tests__/filter-payload.test.ts +193 -0
  9. package/src/server/__tests__/graphql-client.integration.test.ts +98 -0
  10. package/src/server/__tests__/graphql-client.test.ts +508 -0
  11. package/src/server/__tests__/healthz.test.ts +22 -0
  12. package/src/server/__tests__/history.test.ts +111 -0
  13. package/src/server/__tests__/otp-manager.test.ts +132 -0
  14. package/src/server/__tests__/scrape-runner.test.ts +144 -0
  15. package/src/server/__tests__/settings-routes.test.ts +117 -0
  16. package/src/server/__tests__/sources-routes.test.ts +149 -0
  17. package/src/server/__tests__/validate-payload.test.ts +193 -0
  18. package/src/server/__tests__/vault-routes.test.ts +174 -0
  19. package/src/server/__tests__/vault.test.ts +33 -0
  20. package/src/server/__tests__/websocket.test.ts +151 -0
  21. package/src/server/account-discovery.ts +49 -0
  22. package/src/server/accounts-routes.ts +74 -0
  23. package/src/server/check-accounts.ts +79 -0
  24. package/src/server/filter-payload.ts +145 -0
  25. package/src/server/graphql/client.ts +103 -0
  26. package/src/server/graphql/mutations.ts +518 -0
  27. package/src/server/history-routes.ts +11 -0
  28. package/src/server/history.ts +53 -0
  29. package/src/server/index.ts +40 -0
  30. package/src/server/otp-manager.ts +63 -0
  31. package/src/server/payload-schemas/amex.schema.ts +2 -0
  32. package/src/server/payload-schemas/cal.schema.ts +27 -0
  33. package/src/server/payload-schemas/currency-rates.schema.ts +11 -0
  34. package/src/server/payload-schemas/discount.schema.ts +26 -0
  35. package/src/server/payload-schemas/isracard.schema.ts +58 -0
  36. package/src/server/payload-schemas/max.schema.ts +27 -0
  37. package/src/server/payload-schemas/poalim-foreign.schema.ts +30 -0
  38. package/src/server/payload-schemas/poalim-ils.schema.ts +31 -0
  39. package/src/server/payload-schemas/poalim-swift.schema.ts +21 -0
  40. package/src/server/scrape-runner.ts +165 -0
  41. package/src/server/scrapers/__tests__/amex.test.ts +142 -0
  42. package/src/server/scrapers/__tests__/cal.test.ts +135 -0
  43. package/src/server/scrapers/__tests__/currency-rates.test.ts +105 -0
  44. package/src/server/scrapers/__tests__/discount.test.ts +160 -0
  45. package/src/server/scrapers/__tests__/isracard.test.ts +142 -0
  46. package/src/server/scrapers/__tests__/max.test.ts +115 -0
  47. package/src/server/scrapers/__tests__/poalim.test.ts +154 -0
  48. package/src/server/scrapers/amex.ts +63 -0
  49. package/src/server/scrapers/cal.ts +56 -0
  50. package/src/server/scrapers/currency-rates.ts +64 -0
  51. package/src/server/scrapers/discount.ts +62 -0
  52. package/src/server/scrapers/isracard.ts +68 -0
  53. package/src/server/scrapers/max.ts +32 -0
  54. package/src/server/scrapers/poalim.ts +103 -0
  55. package/src/server/settings-routes.ts +27 -0
  56. package/src/server/sources-routes.ts +182 -0
  57. package/src/server/validate-payload.ts +74 -0
  58. package/src/server/vault-routes.ts +99 -0
  59. package/src/server/vault-store.ts +42 -0
  60. package/src/server/vault.ts +216 -0
  61. package/src/server/websocket.ts +454 -0
  62. package/src/shared/source-types.ts +10 -0
  63. package/src/shared/types.ts +20 -0
  64. package/src/shared/ws-protocol.ts +177 -0
  65. package/src/test-setup.ts +6 -0
  66. package/src/ui/__tests__/accounts-tab.test.tsx +134 -0
  67. package/src/ui/__tests__/config.test.tsx +99 -0
  68. package/src/ui/__tests__/history.test.tsx +94 -0
  69. package/src/ui/__tests__/run.test.tsx +195 -0
  70. package/src/ui/__tests__/settings-tab.test.tsx +79 -0
  71. package/src/ui/__tests__/sources-tab.test.tsx +139 -0
  72. package/src/ui/__tests__/vault-setup.test.tsx +105 -0
  73. package/src/ui/__tests__/vault-unlock.test.tsx +78 -0
  74. package/src/ui/app.tsx +109 -0
  75. package/src/ui/components/error-boundary.tsx +54 -0
  76. package/src/ui/components/otp-modal.tsx +82 -0
  77. package/src/ui/components/skeleton.tsx +58 -0
  78. package/src/ui/components/task-row.tsx +241 -0
  79. package/src/ui/contexts/vault-context.tsx +77 -0
  80. package/src/ui/lib/api.ts +117 -0
  81. package/src/ui/lib/ws.ts +137 -0
  82. package/src/ui/main.tsx +9 -0
  83. package/src/ui/screens/config/accounts-tab.tsx +185 -0
  84. package/src/ui/screens/config/config.tsx +163 -0
  85. package/src/ui/screens/config/settings-tab.tsx +167 -0
  86. package/src/ui/screens/config/source-forms.tsx +518 -0
  87. package/src/ui/screens/config/source-types.ts +91 -0
  88. package/src/ui/screens/config/sources-tab.tsx +176 -0
  89. package/src/ui/screens/history.tsx +234 -0
  90. package/src/ui/screens/run.tsx +266 -0
  91. package/src/ui/screens/vault-setup.tsx +120 -0
  92. package/src/ui/screens/vault-unlock.tsx +38 -0
  93. package/tsconfig.json +15 -0
  94. package/tsup.config.ts +10 -0
  95. package/vite.config.ts +24 -0
  96. package/vitest.config.ts +7 -0
@@ -0,0 +1,120 @@
1
+ import { useState, type ReactElement } from 'react';
2
+ import { useVault } from '../contexts/vault-context.js';
3
+
4
+ type Step = 1 | 2 | 3;
5
+
6
+ export function VaultSetup(): ReactElement {
7
+ const vault = useVault();
8
+ const [step, setStep] = useState<Step>(1);
9
+ const [password, setPassword] = useState('');
10
+ const [confirm, setConfirm] = useState('');
11
+ const [serverUrl, setServerUrl] = useState('');
12
+ const [apiKey, setApiKey] = useState('');
13
+ const [localError, setLocalError] = useState<string | null>(null);
14
+ const [loading, setLoading] = useState(false);
15
+
16
+ function handleStep1(e: React.FormEvent) {
17
+ e.preventDefault();
18
+ if (password.length < 8) {
19
+ setLocalError('Password must be at least 8 characters.');
20
+ return;
21
+ }
22
+ if (password !== confirm) {
23
+ setLocalError('Passwords do not match.');
24
+ return;
25
+ }
26
+ setLocalError(null);
27
+ setStep(2);
28
+ }
29
+
30
+ function handleStep2(e: React.FormEvent) {
31
+ e.preventDefault();
32
+ setLocalError(null);
33
+ setStep(3);
34
+ }
35
+
36
+ async function handleStep3(e: React.FormEvent) {
37
+ e.preventDefault();
38
+ setLoading(true);
39
+ await vault.create(password, serverUrl, apiKey);
40
+ setLoading(false);
41
+ }
42
+
43
+ return (
44
+ <div>
45
+ <h1>Setup Vault</h1>
46
+
47
+ {step === 1 && (
48
+ <form onSubmit={handleStep1}>
49
+ <h2>Step 1: Choose master password</h2>
50
+ <div>
51
+ <label htmlFor="setup-password">Master password</label>
52
+ <input
53
+ id="setup-password"
54
+ type="password"
55
+ value={password}
56
+ onChange={e => setPassword(e.target.value)}
57
+ autoFocus
58
+ />
59
+ </div>
60
+ <div>
61
+ <label htmlFor="setup-confirm">Confirm password</label>
62
+ <input
63
+ id="setup-confirm"
64
+ type="password"
65
+ value={confirm}
66
+ onChange={e => setConfirm(e.target.value)}
67
+ />
68
+ </div>
69
+ {localError && <p role="alert">{localError}</p>}
70
+ <button type="submit">Next</button>
71
+ </form>
72
+ )}
73
+
74
+ {step === 2 && (
75
+ <form onSubmit={handleStep2}>
76
+ <h2>Step 2: Server connection</h2>
77
+ <div>
78
+ <label htmlFor="setup-server-url">Server URL</label>
79
+ <input
80
+ id="setup-server-url"
81
+ type="url"
82
+ value={serverUrl}
83
+ onChange={e => setServerUrl(e.target.value)}
84
+ autoFocus
85
+ />
86
+ </div>
87
+ <div>
88
+ <label htmlFor="setup-api-key">API key</label>
89
+ <input
90
+ id="setup-api-key"
91
+ type="text"
92
+ value={apiKey}
93
+ onChange={e => setApiKey(e.target.value)}
94
+ />
95
+ </div>
96
+ {localError && <p role="alert">{localError}</p>}
97
+ <button type="button" onClick={() => setStep(1)}>
98
+ Back
99
+ </button>
100
+ <button type="submit">Next</button>
101
+ </form>
102
+ )}
103
+
104
+ {step === 3 && (
105
+ <form onSubmit={handleStep3}>
106
+ <h2>Step 3: Confirm</h2>
107
+ <p>Server URL: {serverUrl || '(none)'}</p>
108
+ <p>API key: {apiKey ? '••••••••' : '(none)'}</p>
109
+ {vault.error && <p role="alert">{vault.error}</p>}
110
+ <button type="button" onClick={() => setStep(2)}>
111
+ Back
112
+ </button>
113
+ <button type="submit" disabled={loading}>
114
+ {loading ? 'Creating…' : 'Create Vault'}
115
+ </button>
116
+ </form>
117
+ )}
118
+ </div>
119
+ );
120
+ }
@@ -0,0 +1,38 @@
1
+ import { useState, type ReactElement } from 'react';
2
+ import { useVault } from '../contexts/vault-context.js';
3
+
4
+ export function VaultUnlock(): ReactElement {
5
+ const vault = useVault();
6
+ const [password, setPassword] = useState('');
7
+ const [loading, setLoading] = useState(false);
8
+
9
+ async function handleSubmit(e: React.FormEvent) {
10
+ e.preventDefault();
11
+ setLoading(true);
12
+ await vault.unlock(password);
13
+ setLoading(false);
14
+ }
15
+
16
+ return (
17
+ <div>
18
+ <h1>Unlock Vault</h1>
19
+ <form onSubmit={handleSubmit}>
20
+ <div>
21
+ <label htmlFor="password">Master password</label>
22
+ <input
23
+ id="password"
24
+ type="password"
25
+ value={password}
26
+ onChange={e => setPassword(e.target.value)}
27
+ disabled={loading}
28
+ autoFocus
29
+ />
30
+ </div>
31
+ {vault.error && <p role="alert">{vault.error}</p>}
32
+ <button type="submit" disabled={loading}>
33
+ {loading ? 'Unlocking…' : 'Unlock'}
34
+ </button>
35
+ </form>
36
+ </div>
37
+ );
38
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "jsx": "react-jsx",
7
+ "rootDir": "src",
8
+ "outDir": "dist",
9
+ "paths": {
10
+ "@accounter/modern-poalim-scraper": ["../modern-poalim-scraper/src/index"]
11
+ }
12
+ },
13
+ "include": ["src/**/*.ts", "src/**/*.tsx"],
14
+ "exclude": ["tsup.config.ts", "vite.config.ts"]
15
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ entry: ['src/server/index.ts'],
5
+ outDir: 'dist/server',
6
+ format: 'esm',
7
+ target: 'node22',
8
+ sourcemap: true,
9
+ clean: true,
10
+ });
package/vite.config.ts ADDED
@@ -0,0 +1,24 @@
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ const scraperServerPort = Number(process.env.PORT ?? 4001);
5
+
6
+ export default defineConfig({
7
+ plugins: [react()],
8
+ server: {
9
+ proxy: {
10
+ '/api': {
11
+ target: `http://localhost:${scraperServerPort}`,
12
+ changeOrigin: true,
13
+ },
14
+ '/ws': {
15
+ target: `ws://localhost:${scraperServerPort}`,
16
+ ws: true,
17
+ },
18
+ },
19
+ },
20
+ build: {
21
+ outDir: 'dist/ui',
22
+ emptyOutDir: true,
23
+ },
24
+ });
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ setupFiles: ['./src/test-setup.ts'],
6
+ },
7
+ });