Skip to content

Commit fe8d6d4

Browse files
committed
Listing feature
1 parent d8c189b commit fe8d6d4

File tree

7 files changed

+231
-58
lines changed

7 files changed

+231
-58
lines changed

client/src/helpers/api.ts

+12
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,18 @@ const api = {
232232

233233
return response.endpoints;
234234
},
235+
236+
async getCollectionEndpoint(id: number) {
237+
if (!jwt) throw new Error("Unauthorized - getEndpoints");
238+
239+
const response = (
240+
await client.get<{ endpoint: { id: number; uri: string } }>(
241+
`/collection/${id}/endpoint`
242+
)
243+
).data;
244+
245+
return response.endpoint.uri;
246+
},
235247
};
236248

237249
export default api;

client/src/helpers/schemaValidation.ts

+25-14
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Schema } from "../interfaces/Data";
1+
import { Definition, Schema } from "../interfaces/Data";
22
import { deepCopy } from "./utils";
33

44
export class SchemaController {
@@ -118,20 +118,31 @@ export class SchemaController {
118118
return { valid: true, message: "Valid" };
119119
}
120120

121-
getInterfaceString() {
122-
let interf: any = {};
123-
this.definition.forEach((definition) => {
124-
if (Array.isArray(definition.type)) {
125-
const insideValidator = new SchemaController();
126-
insideValidator.define(definition.type);
127-
interf[definition.name] = insideValidator.getInterfaceString;
128-
}
129-
if (definition.type === "reference") {
121+
getInterfaceString(): string {
122+
let interfaceString = "{";
123+
const definition = this.definition;
124+
125+
for (const entry of definition) {
126+
if (Array.isArray(entry.type)) {
127+
const midSC = new SchemaController();
128+
midSC.define(entry.type);
129+
const nestedInterface = midSC.getInterfaceString();
130+
interfaceString += `${entry.name}${
131+
entry.optional ? "?" : ""
132+
}: ${nestedInterface}[];\n`;
133+
} else if (entry.type === "reference") {
134+
interfaceString += `${entry.name}${entry.optional ? "?" : ""}: ${
135+
entry.refers || "unknown"
136+
};\n`;
137+
} else {
138+
interfaceString += `${entry.name}${entry.optional ? "?" : ""}: ${
139+
entry.type
140+
};\n`;
130141
}
131-
interf[`${definition.name}${definition.optional ? "?" : ""}`] =
132-
definition.type;
133-
});
134-
return JSON.stringify(interf);
142+
}
143+
144+
interfaceString += "}\n";
145+
return interfaceString;
135146
}
136147
}
137148

client/src/pages/PanelPage/PanelPage.tsx

+28-8
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { Artifact, Collection } from "../../interfaces/Data";
99
import api from "../../helpers/api";
1010
import { deepCopy } from "../../helpers/utils";
1111
import useQuery from "../../hooks/useQuery";
12+
import useModal from "../../hooks/useModal";
13+
import InterfaceModal from "./components/InterfaceModal";
1214

1315
export default function PanelPage() {
1416
const { id } = useParams();
@@ -60,6 +62,8 @@ export default function PanelPage() {
6062
}
6163
}
6264

65+
const modal = useModal();
66+
6367
return (
6468
<div className="h-[85vh] flex">
6569
<div className="basis-1/6 flex flex-col h-full justify-between">
@@ -113,14 +117,30 @@ export default function PanelPage() {
113117
</button>
114118
</div>
115119

116-
{true && (
117-
<Link
118-
to={`/collection/${selectedCollection}/schema`}
119-
className="px-5 py-2 btn-3 gap-x-2 rounded"
120-
>
121-
<MaterialIcon codepoint="ead3" />
122-
Schema
123-
</Link>
120+
{collection && (
121+
<div className="flex gap-x-10">
122+
<button
123+
className="px-5 py-2 btn-3 gap-x-2 rounded"
124+
onClick={() =>
125+
modal.show(
126+
<InterfaceModal
127+
name={collection.name}
128+
schema={collection.schema}
129+
/>
130+
)
131+
}
132+
>
133+
<MaterialIcon codepoint="f1c6" /> Interface
134+
</button>
135+
136+
<Link
137+
to={`/collection/${selectedCollection}/schema`}
138+
className="px-5 py-2 btn-3 gap-x-2 rounded"
139+
>
140+
<MaterialIcon codepoint="ead3" />
141+
Schema
142+
</Link>
143+
</div>
124144
)}
125145
</div>
126146
)}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { useEffect, useRef, useState } from "react";
2+
import { Icon, icons } from "../../../assets/data/icons";
3+
import api from "../../../helpers/api";
4+
import { Color, colors } from "../../../assets/data/colors";
5+
import ModalDefault from "../../../common/ModalDefault";
6+
import { Definition } from "../../../interfaces/Data";
7+
import { SchemaController } from "../../../helpers/schemaValidation";
8+
9+
export default function InterfaceModal(props: {
10+
name: string;
11+
schema: Definition;
12+
}) {
13+
const [interfaceD, setInterface] = useState<string>("{}");
14+
15+
useEffect(() => {
16+
const sc = new SchemaController();
17+
sc.define(props.schema);
18+
setInterface(sc.getInterfaceString());
19+
}, []);
20+
21+
return (
22+
<ModalDefault type="singleButtonClose">
23+
<div className="p-8 flex flex-col gap-y-3">
24+
<h2 className="font-medium text-2xl tracking-tight">
25+
Use this interface within typescript
26+
</h2>
27+
28+
<textarea
29+
value={`interface ${props.name} ${prettifyInterface(interfaceD)}`}
30+
autoFocus
31+
className="max-w-[50vw] bg-transparent text-front h-[40vh] p-3 border border-primary rounded mt-5 mb-2"
32+
readOnly
33+
/>
34+
</div>
35+
</ModalDefault>
36+
);
37+
}
38+
39+
function prettifyInterface(input: string): string {
40+
const INDENTATION = 2;
41+
const NEWLINE = "\n";
42+
43+
let level = 0;
44+
let formatted = "";
45+
let insideString = false;
46+
47+
for (let i = 0; i < input.length; i++) {
48+
const char = input[i];
49+
50+
if (char === '"' && input[i - 1] !== "\\") {
51+
insideString = !insideString;
52+
}
53+
54+
if (insideString) {
55+
formatted += char;
56+
continue;
57+
}
58+
59+
if (char === "[" || char === "{") {
60+
level++;
61+
formatted += char + NEWLINE + " ".repeat(level * INDENTATION);
62+
} else if (char === "]" || char === "}") {
63+
level--;
64+
formatted = formatted.trimEnd();
65+
formatted += NEWLINE + " ".repeat(level * INDENTATION) + char;
66+
} else if (char === ",") {
67+
formatted += char + NEWLINE + " ".repeat(level * INDENTATION);
68+
} else {
69+
formatted += char;
70+
}
71+
}
72+
73+
return formatted;
74+
}

client/src/pages/PanelPage/components/ListView.tsx

+54-35
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { Definition } from "../../../interfaces/Data";
33
import dummyEntries from "../../../assets/data/entries";
44
import { twMerge } from "tailwind-merge";
55
import MaterialIcon from "../../../common/MaterialIcon";
6+
import api from "../../../helpers/api";
7+
import axios from "axios";
68

79
interface ListViewProps {
810
schema: Definition;
@@ -14,7 +16,23 @@ export default function ListView(props: ListViewProps) {
1416

1517
const [visibility, setVisibility] = useState<object>({});
1618

17-
const content = dummyEntries;
19+
const exposedApiBase = import.meta.env.VITE_PUBLIC_ENDPOINT_BASE;
20+
21+
// const content = dummyEntries;
22+
const [content, setContent] = useState<Array<any>>();
23+
24+
async function loadData() {
25+
const uri = await api.getCollectionEndpoint(props.collectionId);
26+
const cnt = await axios.get<{ id: number; data: object }[]>(
27+
`http://${exposedApiBase}/d/${uri}`.replace("localhost", "127.0.0.1")
28+
);
29+
30+
setContent(cnt.data.map((d) => d.data));
31+
}
32+
33+
useEffect(() => {
34+
loadData();
35+
}, []);
1836

1937
return (
2038
<div className="flex flex-col">
@@ -41,40 +59,41 @@ export default function ListView(props: ListViewProps) {
4159
</label>
4260
))}
4361
</div>
44-
45-
<table className="flex-1 mt-10">
46-
<thead>
47-
<tr>
48-
{Object.keys(content[0]).map((item, key) =>
49-
visibility[item as keyof typeof visibility] ? (
50-
<th className="border h-16 font-medium" key={key}>
51-
{item}
52-
</th>
53-
) : null
54-
)}
55-
</tr>
56-
</thead>
57-
<tbody>
58-
{content.map((val, key) => {
59-
return (
60-
<tr
61-
key={key}
62-
className="relative h-10 hover:bg-secondary hover:bg-opacity-30 cursor-pointer"
63-
>
64-
{Object.keys(content[0]).map((item, key) =>
65-
visibility[item as keyof typeof visibility] ? (
66-
<td className="border pl-2" key={key}>
67-
{JSON.stringify(val[item as keyof typeof val])}
68-
</td>
69-
) : (
70-
<></>
71-
)
72-
)}
73-
</tr>
74-
);
75-
})}
76-
</tbody>
77-
</table>
62+
{content && (
63+
<table className="flex-1 mt-10">
64+
<thead>
65+
<tr>
66+
{Object.keys(content[0]).map((item, key) =>
67+
visibility[item as keyof typeof visibility] ? (
68+
<th className="border h-16 font-medium" key={key}>
69+
{item}
70+
</th>
71+
) : null
72+
)}
73+
</tr>
74+
</thead>
75+
<tbody>
76+
{content.map((val, key) => {
77+
return (
78+
<tr
79+
key={key}
80+
className="relative h-10 hover:bg-secondary hover:bg-opacity-30 cursor-pointer"
81+
>
82+
{Object.keys(content[0]).map((item, key) =>
83+
visibility[item as keyof typeof visibility] ? (
84+
<td className="border pl-2" key={key}>
85+
{JSON.stringify(val[item as keyof typeof val])}
86+
</td>
87+
) : (
88+
<></>
89+
)
90+
)}
91+
</tr>
92+
);
93+
})}
94+
</tbody>
95+
</table>
96+
)}
7897
</div>
7998
);
8099
}

go.work.sum

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
99
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
1010
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
1111
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
12-
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
1312
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
13+
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
1414
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
1515
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1616
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=

server/src/routes/collection.ts

+37
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,43 @@ router.get("/:id", authorisedOnly, async (req, res) => {
2020
res.status(200).send({ collection });
2121
});
2222

23+
router.get("/:id/endpoint", authorisedOnly, async (req, res) => {
24+
if (!req.user) return res.sendStatus(403);
25+
26+
const id = Number(req.params.id);
27+
if (!id) return res.sendStatus(400);
28+
29+
const collection = await prisma.collection.findFirst({
30+
where: { id: id, Artifact: { Database: { userId: req.user.id } } },
31+
select: {
32+
id: true,
33+
name: true,
34+
Artifact: {
35+
select: { name: true, Database: { select: { name: true } } },
36+
},
37+
},
38+
});
39+
40+
if (!collection || !collection.Artifact || !collection.Artifact.Database)
41+
return res.sendStatus(403);
42+
43+
const mongodb_dbname =
44+
`onelot${req.user.id}_${collection.Artifact.Database.name}`.replace(
45+
" ",
46+
""
47+
);
48+
49+
const endpoint = await axios.get(
50+
`${Service.DB_ACCESS}/endpoints?db=${mongodb_dbname}&artifact=${
51+
collection.Artifact.name
52+
}&collections=${JSON.stringify([
53+
{ id: collection.id, name: collection.name },
54+
])}`
55+
);
56+
57+
res.status(200).send({ endpoint: endpoint.data.endpoints[0] });
58+
});
59+
2360
router.post("/:id/entry", authorisedOnly, async (req, res) => {
2461
if (!req.user) return res.sendStatus(403);
2562

0 commit comments

Comments
 (0)