Skip to content

Commit e2189f1

Browse files
committed
feat: update data source imports and enhance SQLite adapter with new features
1 parent cd7e059 commit e2189f1

69 files changed

Lines changed: 1784 additions & 842 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: Release Docker Images
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
tags:
7+
- 'v*'
8+
9+
permissions:
10+
contents: read
11+
packages: write
12+
13+
concurrency:
14+
group: release-docker-${{ github.ref }}
15+
cancel-in-progress: false
16+
17+
jobs:
18+
publish-images:
19+
name: Publish ${{ matrix.image_name }} image
20+
runs-on: ubuntu-latest
21+
strategy:
22+
fail-fast: false
23+
matrix:
24+
include:
25+
- image_name: backend
26+
dockerfile: Dockerfile.backend
27+
- image_name: frontend
28+
dockerfile: Dockerfile.frontend
29+
30+
steps:
31+
- name: Check out repository
32+
uses: actions/checkout@v4
33+
34+
- name: Set up QEMU
35+
uses: docker/setup-qemu-action@v3
36+
37+
- name: Set up Docker Buildx
38+
uses: docker/setup-buildx-action@v3
39+
40+
- name: Log in to GitHub Container Registry
41+
uses: docker/login-action@v3
42+
with:
43+
registry: ghcr.io
44+
username: ${{ github.actor }}
45+
password: ${{ secrets.GITHUB_TOKEN }}
46+
47+
- name: Compute image name
48+
id: image
49+
shell: bash
50+
run: |
51+
repository="$(echo "${GITHUB_REPOSITORY}" | tr '[:upper:]' '[:lower:]')"
52+
echo "repository=${repository}" >> "${GITHUB_OUTPUT}"
53+
echo "image=ghcr.io/${repository}-${{ matrix.image_name }}" >> "${GITHUB_OUTPUT}"
54+
55+
- name: Extract Docker metadata
56+
id: meta
57+
uses: docker/metadata-action@v5
58+
with:
59+
images: ${{ steps.image.outputs.image }}
60+
tags: |
61+
type=semver,pattern={{version}}
62+
type=semver,pattern={{major}}.{{minor}}
63+
type=ref,event=tag
64+
type=raw,value=latest,enable={{is_default_branch}}
65+
type=sha,prefix=sha-
66+
67+
- name: Build and push image
68+
uses: docker/build-push-action@v6
69+
with:
70+
context: .
71+
file: ${{ matrix.dockerfile }}
72+
push: true
73+
platforms: linux/amd64,linux/arm64
74+
tags: ${{ steps.meta.outputs.tags }}
75+
labels: ${{ steps.meta.outputs.labels }}
76+
cache-from: type=gha,scope=${{ matrix.image_name }}
77+
cache-to: type=gha,scope=${{ matrix.image_name }},mode=max
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
name: Release Electron App
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
tags:
7+
- 'v*'
8+
9+
permissions:
10+
contents: write
11+
12+
concurrency:
13+
group: release-electron-${{ github.ref }}
14+
cancel-in-progress: false
15+
16+
jobs:
17+
build-electron:
18+
name: Build ${{ matrix.label }}
19+
runs-on: ${{ matrix.runs_on }}
20+
strategy:
21+
fail-fast: false
22+
matrix:
23+
include:
24+
- label: macOS Apple Silicon
25+
runs_on: macos-14
26+
artifact_name: electron-macos-arm64
27+
- label: macOS Intel
28+
runs_on: macos-13
29+
artifact_name: electron-macos-x64
30+
- label: Windows
31+
runs_on: windows-latest
32+
artifact_name: electron-windows-x64
33+
- label: Linux
34+
runs_on: ubuntu-latest
35+
artifact_name: electron-linux-x64
36+
37+
steps:
38+
- name: Check out repository
39+
uses: actions/checkout@v4
40+
41+
- name: Set up pnpm
42+
uses: pnpm/action-setup@v4
43+
with:
44+
version: 10.2.1
45+
46+
- name: Set up Node.js
47+
uses: actions/setup-node@v4
48+
with:
49+
node-version: 24
50+
cache: pnpm
51+
52+
- name: Install Linux packaging dependencies
53+
if: runner.os == 'Linux'
54+
run: |
55+
sudo apt-get update
56+
sudo apt-get install -y fakeroot rpm
57+
58+
- name: Install workspace dependencies
59+
run: pnpm install --frozen-lockfile
60+
61+
- name: Build Electron distributables
62+
run: pnpm --dir packages/electron make
63+
64+
- name: Upload workflow artifacts
65+
uses: actions/upload-artifact@v4
66+
with:
67+
name: ${{ matrix.artifact_name }}
68+
path: packages/electron/out/make/**/*
69+
if-no-files-found: error
70+
71+
- name: Upload release assets
72+
if: github.event_name == 'release' || startsWith(github.ref, 'refs/tags/')
73+
uses: softprops/action-gh-release@v2
74+
with:
75+
files: packages/electron/out/make/**/*
76+
fail_on_unmatched_files: true

Dockerfile.backend

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
1-
FROM node:24-alpine
1+
FROM node:24-bookworm-slim AS deps
22

33
WORKDIR /app
44

55
RUN corepack enable
6+
RUN apt-get update \
7+
&& apt-get install -y --no-install-recommends python3 make g++ \
8+
&& rm -rf /var/lib/apt/lists/*
69

710
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
811
COPY packages/backend/package.json packages/backend/package.json
912

1013
RUN pnpm install --filter backend... --frozen-lockfile --prod
1114

15+
FROM node:24-bookworm-slim
16+
17+
WORKDIR /app
18+
19+
RUN corepack enable
20+
21+
COPY --from=deps /app/node_modules ./node_modules
22+
COPY --from=deps /app/package.json ./package.json
23+
COPY --from=deps /app/pnpm-lock.yaml ./pnpm-lock.yaml
24+
COPY --from=deps /app/pnpm-workspace.yaml ./pnpm-workspace.yaml
1225
COPY packages/backend packages/backend
1326

1427
WORKDIR /app/packages/backend

Dockerfile.frontend

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM node:24-alpine AS build
1+
FROM node:24-bookworm-slim AS build
22

33
WORKDIR /app
44

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
"main": "index.js",
66
"pnpm": {
77
"onlyBuiltDependencies": [
8+
"better-sqlite3",
89
"electron",
9-
"electron-winstaller"
10+
"electron-winstaller",
11+
"esbuild"
1012
]
1113
},
1214
"scripts": {

packages/backend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@types/express": "^5.0.2",
2424
"@types/express-ws": "^3.0.5",
2525
"body-parser": "^2.2.0",
26+
"better-sqlite3": "^12.8.0",
2627
"cors": "^2.8.5",
2728
"drizzle-orm": "^0.45.1",
2829
"express": "^5.0.2",

packages/backend/src/adapters/database/sql/mysql-adapter/MySQLAdapter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ export class MySQLAdapter extends DefaultSQLAdapter {
163163
where?: string
164164
}): Promise<SQLTableRowPage> {
165165
const page = Math.max(input.page, 1)
166-
const pageSize = Math.min(Math.max(input.pageSize, 1), 200)
166+
const pageSize = Math.max(input.pageSize, 1)
167167
const details = await this.getTableDetails(input.table)
168168
const sortBy = input.sortBy && details.columns.find((column) => column.name === input.sortBy) ? input.sortBy : details.primaryKeys[0] ?? details.columns[0]?.name
169169
const sortDirection = input.sortDirection === 'desc' ? 'desc' : 'asc'

packages/backend/src/adapters/database/sql/postgres-adapter/PostgresAdapter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ export class PostgresAdapter extends DefaultSQLAdapter {
176176
where?: string
177177
}): Promise<SQLTableRowPage> {
178178
const page = Math.max(input.page, 1)
179-
const pageSize = Math.min(Math.max(input.pageSize, 1), 200)
179+
const pageSize = Math.max(input.pageSize, 1)
180180
const details = await this.getTableDetails(input.table)
181181
const sortBy = input.sortBy && details.columns.find((column) => column.name === input.sortBy) ? input.sortBy : details.primaryKeys[0] ?? details.columns[0]?.name
182182
const sortDirection = input.sortDirection === 'desc' ? 'desc' : 'asc'

packages/backend/src/adapters/database/sql/sqlite-adapter/SqliteAdapter.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { DatabaseSync, type SQLInputValue, type SQLOutputValue } from 'node:sqlite'
21
import {
32
DefaultSQLAdapter,
43
type QueryResult,
@@ -10,9 +9,13 @@ import {
109
import { assertIdentifier } from '../shared/identifier.ts'
1110
import { splitSqlStatements } from '../shared/sql-statements.ts'
1211
import { normalizeWhereClause } from '../shared/where-clause.ts'
12+
import { loadSqliteDatabaseConstructor, type SqliteDatabaseLike } from '../../../../shared/sqlite-driver.ts'
13+
14+
type SqliteInputValue = null | string | number | bigint | Uint8Array | Buffer
15+
type SqliteOutputValue = unknown
1316

1417
export class SqliteAdapter extends DefaultSQLAdapter {
15-
private db!: DatabaseSync
18+
private db!: SqliteDatabaseLike
1619

1720
constructor(
1821
private options: {
@@ -29,7 +32,7 @@ export class SqliteAdapter extends DefaultSQLAdapter {
2932

3033
private buildWhereClause(row: Record<string, unknown>, keys: string[]) {
3134
const clauses: string[] = []
32-
const params: SQLInputValue[] = []
35+
const params: SqliteInputValue[] = []
3336

3437
for (const key of keys) {
3538
assertIdentifier(key)
@@ -59,7 +62,7 @@ export class SqliteAdapter extends DefaultSQLAdapter {
5962
return details.primaryKeys.length ? details.primaryKeys : details.columns.map((column) => column.name)
6063
}
6164

62-
private toSqlInputValue(value: unknown): SQLInputValue {
65+
private toSqlInputValue(value: unknown): SqliteInputValue {
6366
if (value === null || value === undefined) {
6467
return null
6568
}
@@ -69,7 +72,7 @@ export class SqliteAdapter extends DefaultSQLAdapter {
6972
}
7073

7174
if (ArrayBuffer.isView(value)) {
72-
return value as SQLInputValue
75+
return value as SqliteInputValue
7376
}
7477

7578
if (typeof value === 'boolean') {
@@ -91,7 +94,7 @@ export class SqliteAdapter extends DefaultSQLAdapter {
9194
return values.map((value) => this.toSqlInputValue(value))
9295
}
9396

94-
private normalizeOutputValue(value: SQLOutputValue): unknown {
97+
private normalizeOutputValue(value: SqliteOutputValue): unknown {
9598
return value
9699
}
97100

@@ -112,6 +115,7 @@ export class SqliteAdapter extends DefaultSQLAdapter {
112115
}
113116

114117
async connect() {
118+
const DatabaseSync = await loadSqliteDatabaseConstructor()
115119
this.db = new DatabaseSync(this.options.filePath)
116120
}
117121

@@ -174,7 +178,7 @@ export class SqliteAdapter extends DefaultSQLAdapter {
174178
where?: string
175179
}): Promise<SQLTableRowPage> {
176180
const page = Math.max(input.page, 1)
177-
const pageSize = Math.min(Math.max(input.pageSize, 1), 200)
181+
const pageSize = Math.max(input.pageSize, 1)
178182
const details = await this.getTableDetails(input.table)
179183
const sortBy = input.sortBy && details.columns.find((column) => column.name === input.sortBy) ? input.sortBy : details.primaryKeys[0] ?? details.columns[0]?.name
180184
const sortDirection = input.sortDirection === 'desc' ? 'desc' : 'asc'
@@ -230,7 +234,10 @@ export class SqliteAdapter extends DefaultSQLAdapter {
230234
}
231235
}
232236

233-
const result = statement.run(...sqlParams)
237+
const result = statement.run(...sqlParams) as {
238+
changes?: number | bigint
239+
lastInsertRowid?: number | bigint
240+
}
234241
return {
235242
type: 'RESULT',
236243
result: {

packages/backend/src/auth/permissions.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ function withPermissionAccess(targets: string[], access?: PermissionAccess) {
2424
return Array.from(new Set(targets.map((target) => applyPermissionAccess(target, access))))
2525
}
2626

27+
function uniqueTargets(targets: string[]) {
28+
return Array.from(new Set(targets))
29+
}
30+
2731
export function permissionPatternMatches(pattern: string, requiredPermission: string) {
2832
if (pattern === '*') {
2933
return true
@@ -82,6 +86,54 @@ export function dataSourcePermissionTargets(
8286
)
8387
}
8488

89+
export function projectDataSourcePermissionTargets(
90+
projectId: string,
91+
sourceId: string | '*',
92+
access?: PermissionAccess,
93+
) {
94+
const targets = [
95+
`project.manage.${projectId}.datasources`,
96+
`project.manage.${projectId}.datasources.*`,
97+
]
98+
99+
if (sourceId !== '*') {
100+
targets.push(`project.manage.${projectId}.datasources.${sourceId}`)
101+
}
102+
103+
return withPermissionAccess(targets, access)
104+
}
105+
106+
export function dataSourceReadPermissionTargets(projectId: string, sourceId: string | '*') {
107+
return uniqueTargets([
108+
...projectDataSourcePermissionTargets(projectId, sourceId, 'read'),
109+
...projectDataSourcePermissionTargets(projectId, sourceId, 'write'),
110+
...dataSourcePermissionTargets(projectId, sourceId, 'view', 'read'),
111+
...dataSourcePermissionTargets(projectId, sourceId, 'query', 'read'),
112+
...dataSourcePermissionTargets(projectId, sourceId, 'query', 'write'),
113+
...dataSourcePermissionTargets(projectId, sourceId, 'manage', 'write'),
114+
...dataSourcePermissionTargets(projectId, sourceId, 'table.edit', 'write'),
115+
...projectPermissionTargets(projectId, 'manage', 'write'),
116+
])
117+
}
118+
119+
export function dataSourceWritePermissionTargets(projectId: string, sourceId: string | '*') {
120+
return uniqueTargets([
121+
...projectDataSourcePermissionTargets(projectId, sourceId, 'write'),
122+
...dataSourcePermissionTargets(projectId, sourceId, 'query', 'write'),
123+
...dataSourcePermissionTargets(projectId, sourceId, 'manage', 'write'),
124+
...dataSourcePermissionTargets(projectId, sourceId, 'table.edit', 'write'),
125+
...projectPermissionTargets(projectId, 'manage', 'write'),
126+
])
127+
}
128+
129+
export function dataSourceConfigPermissionTargets(projectId: string, sourceId: string | '*') {
130+
return uniqueTargets([
131+
...projectDataSourcePermissionTargets(projectId, sourceId, 'write'),
132+
...dataSourcePermissionTargets(projectId, sourceId, 'manage', 'write'),
133+
...projectPermissionTargets(projectId, 'manage', 'write'),
134+
])
135+
}
136+
85137
export function adminPermissionTargets(
86138
action: 'access' | 'users' | 'roles' | 'apiKeys',
87139
access?: PermissionAccess,

0 commit comments

Comments
 (0)