Skip to content

Commit 4692060

Browse files
committed
feat: implement SQL source management and connection handling
- Added `Channel` class for WebSocket communication. - Introduced `connections-store` for managing active connections. - Created `SourcesSidebar` and `SidebarSQLSource` components for displaying and interacting with SQL sources. - Integrated `SQLColumnEditor` for editing SQL column properties. - Updated `HomeView` to include a dialog for editing columns. - Enhanced `App.vue` with a new `SourcesSidebar` component. - Added support for dynamic table retrieval from SQL sources. - Included loading indicators and connection status in UI components.
1 parent 22ad006 commit 4692060

21 files changed

Lines changed: 457 additions & 146 deletions

File tree

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,13 @@
2121
- [ ] Rest API
2222
- [ ] GraphQL
2323
- [ ] gRPC
24-
- [ ] OpenAPI
24+
- [ ] OpenAPI
25+
26+
27+
## TODOs
28+
- [ ] Add authentication
29+
- [ ] Add projects
30+
- [ ] Electron save passwords via npm package `keytar`
31+
32+
## Backlog
33+
- [ ] Add OpenID Connect support

packages/backend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@types/express": "^5.0.2",
1717
"@types/express-ws": "^3.0.5",
1818
"body-parser": "^2.2.0",
19+
"cors": "^2.8.5",
1920
"express": "^5.0.2",
2021
"express-ws": "^5.0.2",
2122
"mysql2": "^3.14.1",
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import {MySQLAdapter} from "./mysql-adapter/MySQLAdapter";
2+
3+
4+
export const SQL_ADAPTERS = {
5+
'mysql': MySQLAdapter
6+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export class MySQLAdapter extends DefaultSQLAdapter {
2828
async getTables() {
2929
const [rows, fields] = await this.connection!.query(`SELECT * FROM information_schema.tables where table_schema='${this.options.database}' `);
3030

31+
console.log(rows)
32+
3133
return (rows as any[]).map((r) => ({
3234
name: r.TABLE_NAME
3335
}))

packages/backend/src/api/sources/database/sql/websocket.ts

Whitespace-only changes.

packages/backend/src/wsapi/sources/index.ts renamed to packages/backend/src/api/sources/index.ts

Lines changed: 32 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,45 @@
11
import expressWs from "express-ws";
2-
import {sources} from "../../index";
3-
import WebSocket from "ws";
2+
import {Channel, sources} from "../../index";
43
import {DefaultSQLAdapter} from "../../adapters/database/sql/default-sql-adapter/DefaultSQLAdapter";
54
import query from "../../adapters/database/sql/default-sql-adapter/routes/query";
5+
import {SQL_ADAPTERS} from "../../adapters/database/sql";
6+
import {Request, Response} from "express";
7+
import {MySQLAdapter} from "../../adapters/database/sql/mysql-adapter/MySQLAdapter";
68

7-
export class Channel {
8-
id: string
9-
ws: WebSocket.WebSocket
10-
source: string
11-
adapter: DefaultSQLAdapter
12-
13-
constructor(id: string, ws: WebSocket.WebSocket, source: string, adapter: DefaultSQLAdapter) {
14-
this.id = id
15-
this.ws = ws
16-
this.source = source
17-
this.adapter = adapter
18-
}
9+
const routes = {
10+
query
11+
}
1912

20-
send(data: any) {
21-
this.ws.send(JSON.stringify({
22-
id: this.id,
23-
...data
24-
}))
25-
}
13+
export function setupSourcesApi(server: expressWs.Application) {
2614

27-
error() {
28-
this.send({
29-
type: 'error',
30-
error: true
15+
server.get("/api/projects/:project/sources", (req, res) => {
16+
console.log('Getting sources')
17+
const sourcesList = Object.keys(sources).map((key) => {
18+
return {
19+
name: key,
20+
type: sources[key].type
21+
}
3122
})
32-
}
23+
res.json(sourcesList)
24+
})
3325

34-
close() {
35-
this.send(JSON.stringify({
36-
type: 'close',
37-
}))
38-
}
39-
}
4026

41-
const routes = {
42-
query
43-
}
27+
server.get('/api/projects/:project/sources/:source/tables', async (req: Request, res: Response) => {
28+
const source = sources[req.params.source]
29+
if (!source) {
30+
res.status(404).send('Source not found')
31+
return;
32+
}
4433

34+
const adapter = new MySQLAdapter(source.options)
35+
await adapter.connect()
36+
const tables = await adapter.getTables()
37+
await adapter.close()
4538

46-
export function setupSourcesApi(server: expressWs.Application) {
47-
console.log('Setting up /api/organizations/:orga/projects/:project/source/:source/sql')
48-
server.ws('/api/organizations/:orga/projects/:project/source/:source/sql', (ws, req) => {
39+
res.json(tables)
40+
})
41+
42+
server.ws('/api/projects/:project/sources/:source/sql', (ws, req) => {
4943
console.log('Creating websocket')
5044
const source = sources[req.params.source]
5145
if (!source) {
@@ -67,7 +61,7 @@ export function setupSourcesApi(server: expressWs.Application) {
6761

6862
if (data.type === 'connect') {
6963
console.log('Connecting', source.options)
70-
adapter = new source.adapter(source.options)
64+
adapter = new SQL_ADAPTERS[source.type](source.options)
7165
try {
7266
await adapter.connect()
7367
channel.close()

packages/backend/src/index.ts

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import bodyParser from 'body-parser'
22
import express, { Request, Response } from 'express'
33
import expressWs from 'express-ws'
4-
import {setupSourcesApi} from "./wsapi/sources";
4+
import cors from 'cors'
5+
import {setupSourcesApi} from "./api/sources";
56
import {MySQLAdapter} from "./adapters/database/sql/mysql-adapter/MySQLAdapter";
67

78
export const sources = {
89
pastefy: {
9-
type: 'sql',
10-
adapter: MySQLAdapter,
10+
type: 'mysql',
1111
options: {
12+
adapter: MySQLAdapter,
1213
host: 'localhost',
1314
user: 'pastefy',
1415
password: 'pastefy',
@@ -19,21 +20,7 @@ export const sources = {
1920

2021
const server = expressWs(express()).app
2122
server.use(bodyParser.json())
22-
23-
server.get('/sources/:source/tables', async (req: Request, res: Response) => {
24-
const source = sources[req.params.source]
25-
if (!source) {
26-
res.status(404).send('Source not found')
27-
return;
28-
}
29-
30-
const adapter = new MySQLAdapter(source.options)
31-
await adapter.connect()
32-
const tables = await adapter.getTables()
33-
await adapter.close()
34-
35-
res.json(tables)
36-
})
23+
server.use(cors())
3724

3825
setupSourcesApi(server)
3926

@@ -42,4 +29,5 @@ async function main() {
4229
}
4330

4431

45-
main().catch(console.error);
32+
main().catch(console.error);
33+
export {Channel} from "./ws/Channel";

packages/backend/src/ws/Channel.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {DefaultSQLAdapter} from "../adapters/database/sql/default-sql-adapter/DefaultSQLAdapter";
2+
import * as WebSocket from "ws";
3+
4+
export class Channel {
5+
id: string
6+
ws: WebSocket.WebSocket
7+
source: string
8+
adapter: DefaultSQLAdapter
9+
10+
constructor(id: string, ws: WebSocket.WebSocket, source: string, adapter: DefaultSQLAdapter) {
11+
this.id = id
12+
this.ws = ws
13+
this.source = source
14+
this.adapter = adapter
15+
}
16+
17+
send(data: any) {
18+
this.ws.send(JSON.stringify({
19+
id: this.id,
20+
...data
21+
}))
22+
}
23+
24+
error() {
25+
this.send({
26+
type: 'error',
27+
error: true
28+
})
29+
}
30+
31+
close() {
32+
this.send(JSON.stringify({
33+
type: 'close',
34+
}))
35+
}
36+
}

packages/frontend/src/App.vue

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import Tree from 'primevue/tree'
33
import Button from 'primevue/button'
44
import { onMounted, ref } from 'vue'
55
import ResizeKnob from '@/components/ResizeKnob.vue'
6+
import { client } from '@/main.ts'
7+
import { useAsyncState } from '@vueuse/core'
8+
import SourcesSidebar from '@/components/SourcesSidebar.vue'
69
710
const nodes = ref([
811
{
@@ -75,12 +78,6 @@ const sideBarWidth = ref(300)
7578
</router-link>
7679

7780
<div class="flex flex-col gap-2 p-1.5 pt-0">
78-
<!--<router-link
79-
to="/"
80-
class="w-full aspect-square flex justify-center items-center bg-primary-500/20 rounded-lg"
81-
>
82-
<i class="text-primary-500 text-xl ti ti-device-laptop" />
83-
</router-link> -->
8481
<router-link
8582
to="/"
8683
class="w-full aspect-square flex justify-center items-center bg-primary-500/20 rounded-lg"
@@ -100,39 +97,12 @@ const sideBarWidth = ref(300)
10097
to="/"
10198
class="w-full aspect-square flex justify-center items-center border border-primary-500/20 rounded-full"
10299
>
103-
<span class="text-primary-500 text-lg font-bold select-none">U</span>
100+
<i class="text-primary-500 text-xl ti ti-device-laptop" />
104101
</router-link>
105102
</div>
106103
</div>
107104
</div>
108-
<div class="border-r border-neutral-200 dark:border-neutral-800 px-0.5 relative">
109-
<div class="flex w-full justify-between items-center p-2 pb-0 pt-0.5 pr-0 region-drag">
110-
<span class="opacity-60">sources</span>
111-
<Button
112-
icon="ti ti-plus text-lg p-0.5"
113-
size="small"
114-
rounded
115-
severity="contrast"
116-
text
117-
class="region-no-drag"
118-
/>
119-
</div>
120-
<Tree
121-
v-model:selectionKeys="selectedKey"
122-
v-model:expandedKeys="expandedKeys"
123-
size="small"
124-
:value="nodes"
125-
selectionMode="single"
126-
class="w-full bg-transparent p-0 text-sm"
127-
/>
128-
129-
<ResizeKnob
130-
:min-width="230"
131-
:max-width="460"
132-
v-model:width="sideBarWidth"
133-
class="absolute right-[-0.3rem] top-0"
134-
/>
135-
</div>
105+
<SourcesSidebar v-model:sidebar-width="sideBarWidth" />
136106
<main class="h-full w-full overflow-hidden">
137107
<RouterView />
138108
</main>
Lines changed: 14 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)