Skip to content

Commit

Permalink
Merge pull request #201 from gridaco/database/views
Browse files Browse the repository at this point in the history
Unified Reference Search & Secure form agent xsb fk search pattern
  • Loading branch information
softmarshmallow authored Oct 10, 2024
2 parents a984161 + e4abf87 commit cef7563
Show file tree
Hide file tree
Showing 45 changed files with 960 additions and 651 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { grida_forms_client } from "@/lib/supabase/server";
import {
GridaXSupabaseService,
createXSupabaseClient,
} from "@/services/x-supabase";
import { FormFieldReferenceSchema, GridaXSupabase } from "@/types";
import assert from "assert";
import { notFound } from "next/navigation";
import { GridaXSupabaseService } from "@/services/x-supabase";
import { type FormFieldReferenceSchema, GridaXSupabase } from "@/types";
import { NextRequest, NextResponse } from "next/server";
import { notFound } from "next/navigation";
import assert from "assert";

//
// TODO: consider dropping the `reference` field from the field schema - on x-sb, it's not needed
//
/**
* [search/meta] This endpoint serves the meta information for the search action.
* since we support db connection and search field on form can be a potential security risk,
* this endpoint only provides the meta information of the search field, and how the actual query can be made.
*/
export async function GET(
req: NextRequest,
context: {
Expand All @@ -22,13 +21,6 @@ export async function GET(
) {
const { session: session_id, field: field_id } = context.params;

const _q_page = req.nextUrl.searchParams.get("page");
const page = _q_page ? parseInt(_q_page) : 1;
const _q_per_page = req.nextUrl.searchParams.get("per_page");
const per_page = _q_per_page ? parseInt(_q_per_page) : 50;

// FIXME: Strict Authorization - this route accesses auth.users

const { data, error } = await grida_forms_client
.from("response_session")
.select(
Expand Down Expand Up @@ -65,75 +57,36 @@ export async function GET(
supabase_project: { sb_schema_definitions },
} = conn;

const x_client = await createXSupabaseClient(
supabase_connection?.supabase_project_id,
{
service_role: true,
db: {
schema: schema,
},
}
);

switch (schema) {
case "auth": {
assert(
table === "users",
`Unsupported table "${table}" on schena "${schema}"`
);
const { data, error } = await x_client.auth.admin.listUsers({
page: page,
perPage: per_page,
});

if (error || !data) {
console.error("search/err::user", error);
return NextResponse.error();
}

return NextResponse.json({
meta: {
schema_name: schema as string,
provider: "x-supabase",
supabase_project_id: supabase_connection.supabase_project_id,
schema_name: "auth",
table_name: table,
table_schema: GridaXSupabase.SupabaseUserJsonSchema as any,
column: column,
referenced_column: column,
},
data: data.users,
count: data.total,
// always ok - for xsb auth
error: null,
status: 200,
statusText: "OK",
} satisfies GridaXSupabase.XSBSearchResult<
any,
{
column: string;
}
>);
} satisfies GridaXSupabase.Forms.XSBSearchMetaResult);
}
case "public":
default: {
const r1 = (page - 1) * per_page;
const r2 = r1 + per_page;

const res = await x_client.from(table).select().range(r1, r2);

return NextResponse.json({
...res,
meta: {
provider: "x-supabase",
supabase_project_id: supabase_connection.supabase_project_id,
schema_name: schema,
table_name: table,
column: column,
table_schema: sb_schema_definitions[schema][table],
referenced_column: column,
},
} satisfies GridaXSupabase.XSBSearchResult<
any,
{
column: string;
}
>);

break;
} satisfies GridaXSupabase.Forms.XSBSearchMetaResult);
}
}
}
Expand All @@ -153,6 +106,4 @@ export async function GET(
}
);
}

return NextResponse.json({});
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { SupabaseTableInfo } from "@/scaffolds/x-supabase/supabase-table-info";
import { XSBTableInfo } from "@/scaffolds/x-supabase/xsb-table-info";
import { useEditorState } from "@/scaffolds/editor";
import { XSupabasePrivateApiTypes } from "@/types/private/api";
import { Sector, SectorHeader, SectorHeading } from "@/components/preferences";
Expand Down Expand Up @@ -689,7 +689,7 @@ function ConnectSchema({
<TableIcon className="me-2 inline-flex" />
{schema}.{table}
</Label>
<SupabaseTableInfo
<XSBTableInfo
table={tables[table] as GridaXSupabase.JSONSChema}
/>
</div>
Expand Down Expand Up @@ -840,7 +840,7 @@ function ConnectFormXSupabaseTable({
</Label>

{tableName && sb_schema_definitions[schemaName][tableName] && (
<SupabaseTableInfo
<XSBTableInfo
table={
sb_schema_definitions[schemaName][
tableName
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"use client";

import { PrivateEditorApi } from "@/lib/private";
import { useEditorState } from "@/scaffolds/editor";
import {
GridQueryLimitSelect,
Expand All @@ -15,57 +14,38 @@ import {
import * as GridLayout from "@/scaffolds/grid-editor/components/layout";
import { useEffect, useMemo, useState } from "react";
import { CurrentTable } from "@/scaffolds/editor/utils/switch-table";
import { GridData } from "@/scaffolds/grid-editor/grid-data";
import { XSBAuthUsersGridData } from "@/scaffolds/grid/wellknown/xsb-auth.users-grid-data";
import { EditorSymbols } from "@/scaffolds/editor/symbols";
import useSWR from "swr";
import {
useDataGridTextSearch,
useDataGridQuery,
useDataGridRefresh,
} from "@/scaffolds/editor/use";
import { XSBAuthUsersGrid } from "@/scaffolds/grid/wellknown/xsb-auth.users-grid";
import { XSBUserRow } from "@/scaffolds/grid";
import { GridaXSupabase } from "@/types";
import { useXSBListUsers } from "@/scaffolds/x-supabase/use-xsb-list-users";
import assert from "assert";

export default function XTablePage() {
const [state, dispatch] = useEditorState();
const [total, setTotal] = useState<number>();

const { supabase_project, datagrid_isloading, datagrid_query } = state;
const { supabase_project } = state;

assert(supabase_project);

const refresh = useDataGridRefresh();
const query = useDataGridQuery({ estimated_count: total });
const search = useDataGridTextSearch();

const request = useMemo(() => {
if (!supabase_project) return null;
const request_url = PrivateEditorApi.XSupabase.url_x_auth_users_get(
supabase_project.id,
{
page: (datagrid_query?.q_page_index ?? 0) + 1,
perPage: datagrid_query?.q_page_limit ?? 100,
}
);

if (datagrid_query?.q_refresh_key) {
return request_url + "&r=" + datagrid_query.q_refresh_key;
const { data, isLoading, isValidating } = useXSBListUsers(
supabase_project.id,
{
page: (query?.q_page_index ?? 0) + 1,
perPage: query?.q_page_limit ?? 100,
r: query?.q_refresh_key,
}

return request_url;
}, [datagrid_query]);

const { data, isLoading, isValidating } =
useSWR<GridaXSupabase.ListUsersResult>(
request,
async (url: string) => {
const res = await fetch(url);
return res.json();
},
{
// disable this since this feed replaces (not updates) the data, which causes the ui to refresh, causing certain ux fails (e.g. dialog on cell)
revalidateOnFocus: false,
}
);
);

useEffect(() => {
dispatch({
Expand All @@ -74,18 +54,11 @@ export default function XTablePage() {
});
}, [dispatch, isLoading, isValidating]);

const { filtered, inputlength } = useMemo(() => {
return GridData.rows({
filter: {
empty_data_hidden: state.datagrid_local_filter.empty_data_hidden,
text_search: state.datagrid_query?.q_text_search,
},
table: EditorSymbols.Table.SYM_GRIDA_X_SUPABASE_AUTH_USERS_TABLE_ID,
data: {
rows: data?.data?.users ?? [],
},
const filtered = useMemo(() => {
return XSBAuthUsersGridData.rows(data?.data?.users ?? [], {
search: query?.q_text_search?.query,
});
}, [data, state.datagrid_local_filter, state.datagrid_query?.q_text_search]);
}, [data, query?.q_text_search]);

useEffect(() => {
if (!data?.error) {
Expand All @@ -108,16 +81,15 @@ export default function XTablePage() {
<GridViewSettings />
</GridLayout.HeaderMenus>
</GridLayout.HeaderLine>
<GridLoadingProgressLine />
<GridLoadingProgressLine loading={refresh.refreshing} />
</GridLayout.Header>
<GridLayout.Content>
<XSBAuthUsersGrid
rows={filtered as XSBUserRow[]}
loading={datagrid_isloading}
loading={refresh.refreshing}
mask={state.datagrid_local_filter.masking_enabled}
highlightTokens={
state.datagrid_query?.q_text_search?.query
? [state.datagrid_query.q_text_search.query]
: []
query?.q_text_search?.query ? [query.q_text_search.query] : []
}
/>
</GridLayout.Content>
Expand Down
2 changes: 0 additions & 2 deletions apps/forms/app/editor.css
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
/* https://github.com/adazzle/react-data-grid/issues/3460#issuecomment-2016837753 */
@import "react-data-grid/lib/styles.css";
@import "./ui.css";
18 changes: 18 additions & 0 deletions apps/forms/components/extension/input-skeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from "react";
import { cn } from "@/utils";
import { Skeleton } from "../ui/skeleton";

export function InputSkeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<Skeleton
className={cn(
"flex h-9 w-full rounded-md border border-input",
className
)}
{...props}
/>
);
}
Loading

0 comments on commit cef7563

Please sign in to comment.