Skip to content

Commit

Permalink
supabase 인증
Browse files Browse the repository at this point in the history
  • Loading branch information
if1live committed Mar 23, 2024
1 parent 295aa91 commit 5b401e1
Show file tree
Hide file tree
Showing 10 changed files with 215 additions and 162 deletions.
6 changes: 4 additions & 2 deletions packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
},
"dependencies": {
"@hookform/error-message": "^2.0.1",
"@supabase/auth-ui-react": "^0.4.7",
"@supabase/auth-ui-shared": "^0.1.8",
"@supabase/supabase-js": "^2.39.8",
"@yuuka/core": "workspace:*",
"@yuuka/db": "workspace:*",
"kysely": "^0.27.3",
Expand All @@ -26,8 +29,7 @@
"semantic-ui-css": "^2.5.0",
"semantic-ui-react": "3.0.0-beta.2",
"sql.js": "^1.10.2",
"swr": "^2.2.5",
"use-query-params": "^2.2.1"
"swr": "^2.2.5"
},
"devDependencies": {
"@types/react": "^18.2.67",
Expand Down
20 changes: 9 additions & 11 deletions packages/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ import {
createHashRouter,
createRoutesFromElements,
} from "react-router-dom";
import { QueryParamProvider } from "use-query-params";
import { WindowHistoryAdapter } from "use-query-params/adapters/window";
import "./App.css";
import { AuthenticateProvider } from "./providers/AuthenticateProvider";
import { DataSourceProvider } from "./providers/DataSourceProvider";
import { MasterDataProvider } from "./providers/MasterDataProvider";
import { JournalRouter } from "./routes/JournalRoute";
import { LedgerRouter } from "./routes/LedgerRoute";
import { Root } from "./routes/Root";
import { UserRouter } from "./routes/UserRoute";

const router = createHashRouter(
createRoutesFromElements(
<Route path="/" element={<Root />}>
<Route path="/journal/*" element={<JournalRouter />} />
<Route path="/ledger/*" element={<LedgerRouter />} />
<Route path="/user/*" element={<UserRouter />} />
</Route>,
),
{
Expand All @@ -31,13 +31,11 @@ const router = createHashRouter(
);

export const App = () => (
<QueryParamProvider adapter={WindowHistoryAdapter}>
<DataSourceProvider>
<AuthenticateProvider>
<MasterDataProvider>
<RouterProvider router={router} />
</MasterDataProvider>
</AuthenticateProvider>
</DataSourceProvider>
</QueryParamProvider>
<DataSourceProvider>
<AuthenticateProvider>
<MasterDataProvider>
<RouterProvider router={router} />
</MasterDataProvider>
</AuthenticateProvider>
</DataSourceProvider>
);
10 changes: 10 additions & 0 deletions packages/frontend/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
import { createClient } from "@supabase/supabase-js";

export const TOKEN_SECRET = "helloworld";

const SUPABASE_URL = "https://mjrrhdjdbsaystkbxvup.supabase.co";

// anon key는 어차피 클라가 알아야하니까 대충 때려박음
const SUPABASE_KEY =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1qcnJoZGpkYnNheXN0a2J4dnVwIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NjIzMDI2NzcsImV4cCI6MTk3Nzg3ODY3N30.eC3WDXnaIXy21bI8oGPaHTsLeE0jEmjk2qCDycB-AXc";

export const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
7 changes: 3 additions & 4 deletions packages/frontend/src/contexts/AuthContext.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import type { Session } from "@supabase/supabase-js";
import { createContext } from "react";

export interface AuthState {
userId: number;
authToken: string;
session: Session | null;
}

const defaultValues: AuthState = {
userId: 0,
authToken: "",
session: null,
};

export const AuthContext = createContext(defaultValues);
9 changes: 4 additions & 5 deletions packages/frontend/src/contexts/DataSourceContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,12 @@ type DataSourceValue_Sandbox = {
};

/**
* 백엔드 어딘가 sqlite db 자체를 읽고 쓰는 기능을 만든다.
* supabase storage로 sqlite db 자체를 읽고 쓰는 기능
* 웹에서 장부를 수정할때 필요하다
*/
type DataSourceValue_Network = {
_tag: "network";
type DataSourceValue_Supabase = {
_tag: "supabase";
db: KyselyDB;
username: string;
app: Hono;
};

Expand All @@ -53,7 +52,7 @@ type DataSourceValue_Server = {

export type DataSourceValue =
| DataSourceValue_Sandbox
| DataSourceValue_Network
| DataSourceValue_Supabase
| DataSourceValue_Server;

const defaultValue: DataSourceValue = {
Expand Down
94 changes: 38 additions & 56 deletions packages/frontend/src/providers/AuthenticateProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,50 @@
import { userSpecification } from "@yuuka/core";
import { type PropsWithChildren, useContext } from "react";
import useSWR from "swr";
import { StringParam, useQueryParams } from "use-query-params";
import { Auth } from "@supabase/auth-ui-react";
import { ThemeSupa } from "@supabase/auth-ui-shared";
import type { Session } from "@supabase/supabase-js";
import { type PropsWithChildren, useContext, useEffect, useState } from "react";
import { supabase } from "../constants";
import { AuthContext, type AuthState } from "../contexts/AuthContext";
import { DataSourceContext } from "../contexts/DataSourceContext";
import { setAuthToken } from "../fetchers";

// react-router 안쓰고 query string 손대는 편법
// https://github.com/pbeshai/use-query-params/issues/237#issuecomment-1825975483
export const myQueryParams = {
username: StringParam,
} as const;

// 인증 사용하면 db 업로드, 다운로드 쓸수 있다
export const AuthenticateProvider = (props: PropsWithChildren) => {
const database = useContext(DataSourceContext);

// 샌드박스 모드에서는 인증 안쓴다.
if (database._tag === "sandbox") {
return <>{props.children}</>;
}

// TODO: 서버방식에서는 다른 인증을 쓸거같다
if (database._tag === "server") {
return <>{props.children}</>;
}

const [query, setQuery] = useQueryParams(myQueryParams);
const dataSource = useContext(DataSourceContext);

let username = "";
if (query.username) {
username = query.username;
}
if (database.username) {
username = database.username;
}
if (!username) {
return <div>username not found</div>;
}

const sheet = userSpecification.dataSheet;
const spec = sheet.authenticate;
type Req = (typeof spec)["inout"]["_in"];
type Resp = (typeof spec)["inout"]["_out"];

const req: Req = { username };
const qs = new URLSearchParams(req);
const endpoint = `${userSpecification.resource}${spec.endpoint.path}?${qs}`;
const { data, error, isLoading } = useSWR(endpoint);
const resp = data as Resp;
return dataSource._tag === "supabase" ? (
<AuthenticateProvider_Supabase {...props} />
) : (
<>{props.children}</>
);
};

if (error) {
return <div>failed to authenticate</div>;
}
if (isLoading) {
return <div>loading...</div>;
const AuthenticateProvider_Supabase = (props: PropsWithChildren) => {
const [session, setSession] = useState<Session | null>(null);

useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session);
});

const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
});

return () => subscription.unsubscribe();
}, []);

if (!session) {
return (
<Auth
supabaseClient={supabase}
appearance={{ theme: ThemeSupa }}
providers={[]}
/>
);
}

// TODO: fetcher로 auth token 넘기는 방법 생각나지 않아서 변수 깡을 설정
setAuthToken(resp.authToken);

const store: AuthState = {
userId: resp.userId,
authToken: resp.authToken,
session,
};

return (
Expand Down
59 changes: 14 additions & 45 deletions packages/frontend/src/providers/DataSourceProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ const DataSourceNode_DragAndDrop = (props: Props) => {
const DataSourceNode_Demo = (props: Props) => {
const { setDataSource, setError } = props;

// 예제 파일 뭐로 만들지?
// TODO: 예제 파일 뭐로 만들지?
const url = "/yuuka/sqlite.db";

const load = async () => {
Expand All @@ -122,89 +122,58 @@ const DataSourceNode_Demo = (props: Props) => {
);
};

const DataSourceNode_Authenticate = (props: Props) => {
const DataSourceNode_Supabase = (props: Props) => {
const { setDataSource, setError } = props;

const [username, setUsername] = useState<string>("");

const load = async () => {
try {
// 껍데기만 만들고 나머지 정보는 나중에 채운다
const dialect = await DataSourceValue.createDialect_blank();
const db = DataSourceValue.createKysely(dialect);
await Database.prepareSchema(db);
setDataSource({ _tag: "network", db, username, app: createApp(db) });
setDataSource({ _tag: "supabase", db, app: createApp(db) });
} catch (e) {
setError(e as Error);
}
};

return (
<>
<h3>sign in</h3>
<Form>
<FormField>
<label>username</label>
<input type="text" onChange={(e) => setUsername(e.target.value)} />
</FormField>

<FormField>
<label>password</label>
<input type="password" />
</FormField>

<FormField>
<Button type="submit" onClick={load} disabled={username.length === 0}>
sign in
</Button>
</FormField>
</Form>
<h3>supabase</h3>
<Button type="button" onClick={load}>
authenticate
</Button>
</>
);
};

const DataSourceNode_Server = (props: Props) => {
const { setDataSource, setError } = props;

const [hostname, setHostname] = useState("127.0.0.1");
const [port, setPort] = useState(3000);
const [endpoint, setEndpoint] = useState("http://127.0.0.1:3000");

const load = async () => {
try {
const endpoint = `://${hostname}:${port}`;
setDataSource({ _tag: "server", endpoint });
} catch (e) {
setError(e as Error);
}
setDataSource({ _tag: "server", endpoint });
};

return (
<>
<h3>api server</h3>
<Form>
<FormField>
<label>hostname</label>
<label>endpoint</label>
<input
type="text"
onChange={(e) => setHostname(e.target.value)}
value={hostname}
/>
</FormField>

<FormField>
<label>port</label>
<input
type="number"
onChange={(e) => setPort(e.target.valueAsNumber)}
value={port}
onChange={(e) => setEndpoint(e.target.value)}
value={endpoint}
/>
</FormField>

<FormField>
<Button
type="submit"
onClick={() => load()}
disabled={hostname.length === 0}
disabled={endpoint.length === 0}
>
connect
</Button>
Expand Down Expand Up @@ -241,7 +210,7 @@ export const DataSourceProvider = (props: PropsWithChildren) => {
setError={setError}
/>

<DataSourceNode_Authenticate
<DataSourceNode_Supabase
setDataSource={setDataSource}
setError={setError}
/>
Expand Down
16 changes: 10 additions & 6 deletions packages/frontend/src/routes/Root.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import { useContext } from "react";
import { Link, Outlet, useLocation } from "react-router-dom";
import { Container, Menu } from "semantic-ui-react";
import { AuthContext } from "../contexts/AuthContext";
import { Button, Container, Menu } from "semantic-ui-react";

export const Root = () => {
const loc = useLocation();
const auth = useContext(AuthContext);

const checkActive = (prefix: string) => loc.pathname.startsWith(prefix);

return (
<>
<Container>
<Menu fiexed="top">
<Menu fiexed="top" size="small">
<Menu.Item as={Link} to="/" header>
yuuka
</Menu.Item>
Expand All @@ -25,7 +22,14 @@ export const Root = () => {
ledger
</Menu.Item>

<Menu.Item position="right">UserID: {auth.userId}</Menu.Item>
<Menu.Item
as={Link}
to="/user"
active={checkActive("/user")}
position="right"
>
user
</Menu.Item>
</Menu>
</Container>

Expand Down
Loading

0 comments on commit 5b401e1

Please sign in to comment.