Skip to content

Commit de93b88

Browse files
committed
updates
1 parent 5a65117 commit de93b88

34 files changed

Lines changed: 584 additions & 410 deletions

apps/api/orpc/routers/connections/create.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { db } from '@conar/db'
22
import { connections, connectionsInsertSchema } from '@conar/db/schema'
33
import { SyncType } from '@conar/shared/enums/sync-type'
44
import { encrypt } from '@conar/shared/utils/crypto-node'
5+
import { sleep } from '@conar/shared/utils/helpers'
56
import { SafeURL } from '@conar/shared/utils/safe-url'
67
import { type } from 'arktype'
78
import { authMiddleware, orpc } from '~/orpc'
@@ -15,6 +16,8 @@ export const create = orpc
1516
.handler(async ({ context, input }) => {
1617
const userSecret = await context.getUserSecret()
1718

19+
await sleep(2000)
20+
1821
const inserted = await db.insert(connections).values(await Promise.all(input.map(async (item) => {
1922
const newConnectionString = new SafeURL(item.connectionString)
2023

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import type { Collection } from '@tanstack/react-db'
2+
import { isLocalhostConnectionString } from '@conar/connection/utils'
3+
import { decryptWithKey, encryptWithKey } from '@conar/shared/utils/crypto-web'
4+
import { decryptWithPrivateKey, generateEncryptionKeyPair } from '@conar/shared/utils/pair-keys'
5+
import { SafeURL } from '@conar/shared/utils/safe-url'
6+
import { persistedCollectionOptions } from '@tanstack/browser-db-sqlite-persistence'
7+
import { BasicIndex, createCollection } from '@tanstack/react-db'
8+
import { encryptionKeyStorage, getEncryptionKey, resetEncryptionKey } from '~/lib/encryption-key-storage'
9+
import { orpc } from '~/lib/orpc'
10+
import { persistence } from '~/lib/sync'
11+
12+
interface StoredConnectionString {
13+
id: string
14+
encrypted: string
15+
updatedAt: Date
16+
metadata: {
17+
isPasswordPopulated: boolean
18+
isLocalhost: boolean
19+
displayUrl: string
20+
defaultResourceName: string | null
21+
}
22+
}
23+
24+
const resolvePromises = new Map<string, Promise<void>>()
25+
26+
async function encryptValue(connectionString: string) {
27+
return encryptWithKey(await getEncryptionKey(), connectionString)
28+
}
29+
30+
async function decryptValue(encryptedConnectionString: string) {
31+
return decryptWithKey(await getEncryptionKey(), encryptedConnectionString)
32+
}
33+
34+
function buildRecord(id: string, connectionString: string, encrypted: string, updatedAt: Date): StoredConnectionString {
35+
const url = new SafeURL(connectionString)
36+
37+
return {
38+
id,
39+
encrypted,
40+
updatedAt,
41+
metadata: {
42+
isPasswordPopulated: !!url.password,
43+
isLocalhost: isLocalhostConnectionString(connectionString),
44+
displayUrl: `${url.hostname}${url.port ? `:${url.port}` : ''}`,
45+
defaultResourceName: url.pathname && url.pathname !== '/' ? url.pathname.slice(1) : null,
46+
},
47+
}
48+
}
49+
50+
// eslint-disable-next-line ts/consistent-type-definitions
51+
type ConnectionStringsUtils = {
52+
decrypt: (id: string) => Promise<string>
53+
upsert: (id: string, connectionString: string, updatedAt: Date) => Promise<void>
54+
ready: () => Promise<void>
55+
resolve: (id: string) => Promise<void>
56+
}
57+
58+
type ConnectionStringsCollection = Collection<StoredConnectionString, string, ConnectionStringsUtils>
59+
60+
export function createConnectionStringsCollection() {
61+
const connectionStringsCollection: ConnectionStringsCollection = createCollection(persistedCollectionOptions<StoredConnectionString, string, never, ConnectionStringsUtils>({
62+
id: 'connection-strings',
63+
persistence,
64+
autoIndex: 'eager',
65+
gcTime: 1,
66+
defaultIndexType: BasicIndex,
67+
schemaVersion: 1,
68+
getKey: item => item.id,
69+
utils: {
70+
async decrypt(id: string): Promise<string> {
71+
const record = connectionStringsCollection.get(id)
72+
if (!record)
73+
throw new Error(`No connection string found for connection "${id}"`)
74+
75+
try {
76+
return await decryptValue(record.encrypted)
77+
}
78+
catch (error) {
79+
await resetEncryptionKey()
80+
throw error
81+
}
82+
},
83+
async upsert(id: string, connectionString: string, updatedAt: Date): Promise<void> {
84+
const encrypted = await encryptValue(connectionString)
85+
const record = buildRecord(id, connectionString, encrypted, updatedAt)
86+
87+
if (connectionStringsCollection.has(id)) {
88+
connectionStringsCollection.update(id, draft => Object.assign(draft, record))
89+
}
90+
else {
91+
connectionStringsCollection.insert(record)
92+
}
93+
},
94+
async ready() {
95+
await Promise.all([
96+
encryptionKeyStorage.ready,
97+
connectionStringsCollection.stateWhenReady(),
98+
Promise.allSettled(resolvePromises.values()),
99+
])
100+
},
101+
async resolve(id: string) {
102+
// await connectionStringsCollection.waitFor('index:added')
103+
const existing = resolvePromises.get(id)
104+
if (existing)
105+
return existing
106+
107+
const local = connectionStringsCollection.get(id)
108+
109+
const promise = (async () => {
110+
const { publicKey, privateKey } = await generateEncryptionKeyPair()
111+
const result = await orpc.connections.resolve.call({ id, publicKey, updatedAt: local?.updatedAt })
112+
113+
if (result.status === 'unchanged')
114+
return
115+
116+
const connectionString = await decryptWithPrivateKey(privateKey, result.connectionString)
117+
await connectionStringsCollection.utils.upsert(id, await preserveLocalPassword(id, connectionString), result.updatedAt)
118+
})().finally(() => {
119+
resolvePromises.delete(id)
120+
})
121+
122+
resolvePromises.set(id, promise)
123+
return promise
124+
},
125+
},
126+
}))
127+
128+
async function preserveLocalPassword(id: string, connectionString: string) {
129+
const url = new SafeURL(connectionString)
130+
const local = connectionStringsCollection.get(id)
131+
132+
if (!url.password && local?.metadata.isPasswordPopulated) {
133+
url.password = new SafeURL(await connectionStringsCollection.utils.decrypt(id)).password
134+
}
135+
136+
return url.toString()
137+
}
138+
139+
return {
140+
connectionStringsCollection,
141+
}
142+
}

apps/app/src/entities/connection/generators/formats/sql.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function generateQuerySQL({
1818
dialect = ConnectionType.Postgres,
1919
}: QueryParams) {
2020
const db = coldDialects[dialect]()
21-
const base = db.$extendTables<{ [table]: Record<string, unknown> }>().selectFrom(table).selectAll()
21+
const base = db.withTables<{ [table]: Record<string, unknown> }>().selectFrom(table).selectAll()
2222
const query = filters.length > 0 ? base.where(eb => buildWhere(eb, filters)) : base
2323
const compiled = query.compile()
2424
return formatSql(inlineParameters(compiled.sql, compiled.parameters), dialect)

apps/app/src/entities/connection/queries/delete-rows.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,31 +10,31 @@ export const deleteRowsQuery = memoize(({ table, schema, primaryKeys }: {
1010
query: {
1111
postgres: db => db
1212
.withSchema(schema)
13-
.$extendTables<{ [table]: Record<string, unknown> }>()
13+
.withTables<{ [table]: Record<string, unknown> }>()
1414
.deleteFrom(table)
1515
.where(({ or, and, eb }) => or(primaryKeys.map(pk => and(
1616
Object.entries(pk).map(([key, value]) => eb(key, '=', value)),
1717
))))
1818
.execute(),
1919
mysql: db => db
2020
.withSchema(schema)
21-
.$extendTables<{ [table]: Record<string, unknown> }>()
21+
.withTables<{ [table]: Record<string, unknown> }>()
2222
.deleteFrom(table)
2323
.where(({ or, and, eb }) => or(primaryKeys.map(pk => and(
2424
Object.entries(pk).map(([key, value]) => eb(key, '=', value)),
2525
))))
2626
.execute(),
2727
mssql: db => db
2828
.withSchema(schema)
29-
.$extendTables<{ [table]: Record<string, unknown> }>()
29+
.withTables<{ [table]: Record<string, unknown> }>()
3030
.deleteFrom(table)
3131
.where(({ or, and, eb }) => or(primaryKeys.map(pk => and(
3232
Object.entries(pk).map(([key, value]) => eb(key, '=', value)),
3333
))))
3434
.execute(),
3535
clickhouse: db => db
3636
.withSchema(schema)
37-
.$extendTables<{ [table]: Record<string, unknown> }>()
37+
.withTables<{ [table]: Record<string, unknown> }>()
3838
.deleteFrom(table)
3939
.where(({ or, and, eb }) => or(primaryKeys.map(pk => and(
4040
Object.entries(pk).map(([key, value]) => eb(key, '=', value)),

apps/app/src/entities/connection/queries/distinct.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,31 @@ export function distinctQuery({ schema, table, column, limit = 1000 }: {
1414
query: {
1515
postgres: db => db
1616
.withSchema(schema)
17-
.$extendTables<{ [table: string]: Record<string, unknown> }>()
17+
.withTables<{ [table: string]: Record<string, unknown> }>()
1818
.selectFrom(table)
1919
.select(column)
2020
.distinct()
2121
.limit(limit)
2222
.execute(),
2323
mysql: db => db
2424
.withSchema(schema)
25-
.$extendTables<{ [table: string]: Record<string, unknown> }>()
25+
.withTables<{ [table: string]: Record<string, unknown> }>()
2626
.selectFrom(table)
2727
.select(column)
2828
.distinct()
2929
.limit(limit)
3030
.execute(),
3131
mssql: db => db
3232
.withSchema(schema)
33-
.$extendTables<{ [table: string]: Record<string, unknown> }>()
33+
.withTables<{ [table: string]: Record<string, unknown> }>()
3434
.selectFrom(table)
3535
.select(column)
3636
.distinct()
3737
.limit(limit)
3838
.execute(),
3939
clickhouse: db => db
4040
.withSchema(schema)
41-
.$extendTables<{ [table: string]: Record<string, unknown> }>()
41+
.withTables<{ [table: string]: Record<string, unknown> }>()
4242
.selectFrom(table)
4343
.select(column)
4444
.distinct()

apps/app/src/entities/connection/queries/drop-table.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const dropTableQuery = memoize(({ table, schema, cascade }: { table: stri
66
postgres: (db) => {
77
let query = db
88
.withSchema(schema)
9-
.$extendTables<{ [table]: Record<string, unknown> }>()
9+
.withTables<{ [table]: Record<string, unknown> }>()
1010
.schema
1111
.dropTable(table)
1212

@@ -19,7 +19,7 @@ export const dropTableQuery = memoize(({ table, schema, cascade }: { table: stri
1919
mysql: (db) => {
2020
let query = db
2121
.withSchema(schema)
22-
.$extendTables<{ [table]: Record<string, unknown> }>()
22+
.withTables<{ [table]: Record<string, unknown> }>()
2323
.schema
2424
.dropTable(table)
2525

@@ -32,7 +32,7 @@ export const dropTableQuery = memoize(({ table, schema, cascade }: { table: stri
3232
mssql: (db) => {
3333
let query = db
3434
.withSchema(schema)
35-
.$extendTables<{ [table]: Record<string, unknown> }>()
35+
.withTables<{ [table]: Record<string, unknown> }>()
3636
.schema
3737
.dropTable(table)
3838

@@ -44,7 +44,7 @@ export const dropTableQuery = memoize(({ table, schema, cascade }: { table: stri
4444
},
4545
clickhouse: db => db
4646
.withSchema(schema)
47-
.$extendTables<{ [table]: Record<string, unknown> }>()
47+
.withTables<{ [table]: Record<string, unknown> }>()
4848
.schema
4949
.dropTable(table)
5050
.execute(),

apps/app/src/entities/connection/queries/insert.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,25 @@ export function insertQuery({ schema, table, rows }: {
99
query: {
1010
postgres: db => db
1111
.withSchema(schema)
12-
.$extendTables<{ [table: string]: Record<string, unknown> }>()
12+
.withTables<{ [table: string]: Record<string, unknown> }>()
1313
.insertInto(table)
1414
.values(rows)
1515
.execute(),
1616
mysql: db => db
1717
.withSchema(schema)
18-
.$extendTables<{ [table: string]: Record<string, unknown> }>()
18+
.withTables<{ [table: string]: Record<string, unknown> }>()
1919
.insertInto(table)
2020
.values(rows)
2121
.execute(),
2222
mssql: db => db
2323
.withSchema(schema)
24-
.$extendTables<{ [table: string]: Record<string, unknown> }>()
24+
.withTables<{ [table: string]: Record<string, unknown> }>()
2525
.insertInto(table)
2626
.values(rows)
2727
.execute(),
2828
clickhouse: db => db
2929
.withSchema(schema)
30-
.$extendTables<{ [table: string]: Record<string, unknown> }>()
30+
.withTables<{ [table: string]: Record<string, unknown> }>()
3131
.insertInto(table)
3232
.values(rows)
3333
.execute(),

apps/app/src/entities/connection/queries/rename-columns.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ export const renameColumnQuery = memoize(({ schema, table, oldColumn, newColumn
66
query: {
77
postgres: db => db
88
.withSchema(schema)
9-
.$extendTables<{ [table]: Record<string, unknown> }>()
9+
.withTables<{ [table]: Record<string, unknown> }>()
1010
.schema
1111
.alterTable(table)
1212
.renameColumn(oldColumn, newColumn)
1313
.execute(),
1414
mysql: db => db
1515
.withSchema(schema)
16-
.$extendTables<{ [table]: Record<string, unknown> }>()
16+
.withTables<{ [table]: Record<string, unknown> }>()
1717
.schema
1818
.alterTable(table)
1919
.renameColumn(oldColumn, newColumn)
@@ -23,7 +23,7 @@ export const renameColumnQuery = memoize(({ schema, table, oldColumn, newColumn
2323
},
2424
clickhouse: db => db
2525
.withSchema(schema)
26-
.$extendTables<{ [table]: Record<string, unknown> }>()
26+
.withTables<{ [table]: Record<string, unknown> }>()
2727
.schema
2828
.alterTable(table)
2929
.renameColumn(oldColumn, newColumn)

apps/app/src/entities/connection/queries/rename-table.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,21 @@ export const renameTableQuery = memoize(({ schema, oldTable, newTable }: { schem
66
query: {
77
postgres: db => db
88
.withSchema(schema)
9-
.$extendTables<{ [oldTable]: Record<string, unknown> }>()
9+
.withTables<{ [oldTable]: Record<string, unknown> }>()
1010
.schema
1111
.alterTable(oldTable)
1212
.renameTo(newTable)
1313
.execute(),
1414
mysql: db => db
1515
.withSchema(schema)
16-
.$extendTables<{ [oldTable]: Record<string, unknown> }>()
16+
.withTables<{ [oldTable]: Record<string, unknown> }>()
1717
.schema
1818
.alterTable(oldTable)
1919
.renameTo(newTable)
2020
.execute(),
2121
mssql: db => db
2222
.withSchema(schema)
23-
.$extendTables<{ [oldTable]: Record<string, unknown> }>()
23+
.withTables<{ [oldTable]: Record<string, unknown> }>()
2424
.schema
2525
.alterTable(oldTable)
2626
.renameTo(newTable)

apps/app/src/entities/connection/queries/rows.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export const resourceRowsQuery = memoize(({
6767

6868
let query = db
6969
.withSchema(schema)
70-
.$extendTables<{ [table]: Record<string, unknown> }>()
70+
.withTables<{ [table]: Record<string, unknown> }>()
7171
.selectFrom(table)
7272
.$if(select !== undefined, qb => qb.select(select!))
7373
.$if(select === undefined, qb => qb.selectAll())
@@ -88,7 +88,7 @@ export const resourceRowsQuery = memoize(({
8888

8989
let query = db
9090
.withSchema(schema)
91-
.$extendTables<{ [table]: Record<string, unknown> }>()
91+
.withTables<{ [table]: Record<string, unknown> }>()
9292
.selectFrom(table)
9393
.$if(select !== undefined, qb => qb.select(select!))
9494
.$if(select === undefined, qb => qb.selectAll())
@@ -109,7 +109,7 @@ export const resourceRowsQuery = memoize(({
109109

110110
let query = db
111111
.withSchema(schema)
112-
.$extendTables<{ [table]: Record<string, unknown> }>()
112+
.withTables<{ [table]: Record<string, unknown> }>()
113113
.selectFrom(table)
114114
.$if(select !== undefined, qb => qb.select(select!))
115115
.$if(select === undefined, qb => qb.selectAll())
@@ -131,7 +131,7 @@ export const resourceRowsQuery = memoize(({
131131

132132
let query = db
133133
.withSchema(schema)
134-
.$extendTables<{ [table]: Record<string, unknown> }>()
134+
.withTables<{ [table]: Record<string, unknown> }>()
135135
.selectFrom(table)
136136
.$if(select !== undefined, qb => qb.select(select!))
137137
.$if(select === undefined, qb => qb.selectAll())

0 commit comments

Comments
 (0)