diff --git a/src/components/credentials/Credentials.tsx b/src/components/credentials/Credentials.tsx index 57481d6..c6f25f4 100644 --- a/src/components/credentials/Credentials.tsx +++ b/src/components/credentials/Credentials.tsx @@ -1,13 +1,13 @@ import { createForm } from '@felte/solid'; import reporter from '@felte/reporter-tippy'; import { Button, Dialog, TextField } from '@kobalte/core'; -import { Component, createSignal } from 'solid-js'; +import { Component, createSignal, JSXElement, Show } from 'solid-js'; import 'tippy.js/dist/tippy.css'; interface CredentialsType { username: string; password: string; - hostname?: string; + database: string; } const [adminCredentials, setAdminCredentials] = createSignal(); @@ -155,3 +155,13 @@ const Credentials: Component = (props) => { }; export default Credentials; + +export const CheckCredentials: Component<{ children: JSXElement }> = ( + props +) => { + return ( + }> + {props.children} + + ); +}; diff --git a/src/components/credentials/index.ts b/src/components/credentials/index.ts index 0e971d7..7c91fc7 100644 --- a/src/components/credentials/index.ts +++ b/src/components/credentials/index.ts @@ -1 +1 @@ -export { default, adminCredentials } from './Credentials'; +export { default, adminCredentials, CheckCredentials } from './Credentials'; diff --git a/src/components/user/User.tsx b/src/components/user/User.tsx index 6a2490c..05b86a0 100644 --- a/src/components/user/User.tsx +++ b/src/components/user/User.tsx @@ -1,88 +1,30 @@ import { createForm } from '@felte/solid'; -import { Component, createSignal, Match, Switch } from 'solid-js'; -import { TextField, Button, Dialog } from '@kobalte/core'; - -import './style.css'; -import { createServerAction$ } from 'solid-start/server'; -import PouchDb from 'pouchdb'; +import { Component, createEffect, Match, Switch } from 'solid-js'; +import { TextField, Button } from '@kobalte/core'; +import reporter from '@felte/reporter-tippy'; import { v4 as uuid } from 'uuid'; -import { put } from '~/lib/db'; +import './style.css'; import { useNavigate } from 'solid-start'; -import { toHex } from '~/lib/to-hex'; -import { cloneDeep } from 'lodash'; +import { adminCredentials } from '../credentials'; +import { put } from '~/lib/put'; +import { del } from '~/lib/del'; +import { isFunction } from 'lodash'; interface Props { - values?: any; + values?: () => any; } const User: Component = (props) => { const navigate = useNavigate(); - const [openDialog, setOpenDialog] = createSignal(false); - const [openUsernamePasswordDialog, setOpenUserNamePasswordDialog] = - createSignal(false); + const getValues = () => { + if (!isFunction(props?.values)) { + return null; + } + return props.values(); + }; - const [status, setStatus] = createSignal(); - - const [saving, save] = createServerAction$(async (args: any) => { - const { props, values } = args; - console.log({ - caller: 'User / save', - props, - values, - args, - id: props.values?._id, - }); - const db = new PouchDb('.db'); - const id = props.values?._id ?? `user/${uuid()}`; - await put({ - db, - doc: { - _id: id, - date: new Date().toISOString(), - type: 'user', - ...values, - }, - }); - return id; - }); - - const [dbChecking, dbCheck] = createServerAction$(async (args: any) => { - console.log({ - caller: 'User / dbCheck', - args, - }); - let baseDbInfoAuth; - try { - const baseDb = new PouchDb(cloneDeep(args)); - baseDbInfoAuth = await baseDb.info(); - } catch (error) { - baseDbInfoAuth = { error }; - } - let baseDbInfoAnon; - try { - const baseDb = new PouchDb(args.name); - baseDbInfoAnon = await baseDb.info(); - } catch (error) { - baseDbInfoAnon = { error }; - } - const name = `${args.name}/userdb-${toHex(args.auth.username)}`; - let userDbInfo; - try { - const userDb = new PouchDb({ ...args, name }); - userDbInfo = await userDb.info(); - } catch (error) { - userDbInfo = { error }; - } - let userDbInfoAnon; - try { - const userDb = new PouchDb(name); - userDbInfoAnon = await userDb.info(); - } catch (error) { - userDbInfoAnon = { error }; - } - return { baseDbInfoAuth, baseDbInfoAnon, userDbInfo, userDbInfoAnon }; - }); + const isNew = () => !getValues(); const submitHandler = async (values: any, context: any) => { console.log({ @@ -91,9 +33,10 @@ const User: Component = (props) => { values, context, }); - const id = await save({ values, props }); - if (!props.values) { - navigate(`/user/${encodeURIComponent(id)}`); + const id = getValues()?._id ?? `user:${uuid()}`; + await put(id, values, isNew()); + if (isNew()) { + navigate(`/user/${id}`); } }; @@ -104,42 +47,79 @@ const User: Component = (props) => { const deleteHandler = async () => { console.log({ caller: 'User / deleteHandler', + id: getValues()?._id, props, }); - await save({ props, values: { _deleted: true } }); + await del(getValues()?._id); navigate(`/user/`); }; - const { form, data } = createForm({ + const validationHandler = async (values: any) => { + let errors: any = {}; + const credentials = adminCredentials(); + if (!credentials) { + return errors; + } + const { database, username, password } = credentials; + try { + const response = await fetch(database, { mode: 'cors' }); + const dbStatus = await response.json(); + if (!dbStatus.couchdb) { + errors.database = 'The URL is not a couchdb instance'; + } + } catch (error) { + errors.database = "Can't access the database"; + } + if (!!errors.database) { + return errors; + } + + if (isNew()) { + const authString = `${username}:${password}`; + const headers = new Headers(); + headers.set('Authorization', 'Basic ' + btoa(authString)); + const response = await fetch( + `${database}/_users/org.couchdb.user:${values.username}`, + { + method: 'HEAD', + mode: 'cors', + headers, + } + ); + if (response.status === 200) { + errors.username = 'The user is already existing'; + } + } + + return errors; + }; + + const { form, data, setData, setInitialValues, reset } = createForm({ onSubmit: submitHandler, - initialValues: props?.values, + extend: reporter(), + validate: validationHandler, + initialValues: getValues(), }); - const dbCheckHandler = async () => { - const result = await dbCheck({ - name: data('database'), - auth: { username: data('username'), password: data('password') }, - }); - console.log({ - caller: 'User / dbCheckHandler', - props, - data: data(), - result, - }); - if (result.baseDbInfoAnon.error) { - setStatus('baseError'); - } else if (!result.userDbInfo.error) { - setStatus('OK'); - } else if ( - result.userDbInfoAnon.error.reason === - 'You are not authorized to access this db.' - ) { - setStatus('passwordError'); + createEffect(() => { + console.log({ caller: 'user / createEffect', values: getValues() }); + if (isNew()) { + const credentials = adminCredentials(); + if (!credentials) { + return; + } + const { database } = credentials; + setData('database', database); } else { - setStatus('notFound'); + setInitialValues(getValues()); + reset(); + console.log({ + caller: 'user / createEffect', + values: getValues(), + data: data(), + }); } - setOpenDialog(true); - }; + }); const createUserHandler = async () => { console.log({ @@ -147,117 +127,70 @@ const User: Component = (props) => { props, data: data(), }); - setOpenUserNamePasswordDialog(true); + // setOpenUserNamePasswordDialog(true); }; console.log({ caller: 'User ', props, + values: getValues(), }); return ( - <> -
- - Mail address - - - Please provide a valid URL - - - - Database - - - Please provide a valid URL - - - - User name - - - - Password - - - Please provide a valid password - - - - - Save - Delete - - - Create - - - DB check - Cancel -
- - - -
- -
- - Database check - - - X - -
- - - - The database address seems to be wrong !! - - - The user is already existing. - - -
The user must be created.
- - Create user - -
- -
- The user seems to exist but the password is wrong. -
- - Update password - -
-
-
-
-
-
-
- +
+ + Mail address + + + Please provide a valid URL + + + + Database + + + + User name + + + + Password + + + + + Save + Delete + + + Create + + + Cancel +
); }; diff --git a/src/components/users/Users.tsx b/src/components/users/Users.tsx index ee22e79..9070ad6 100644 --- a/src/components/users/Users.tsx +++ b/src/components/users/Users.tsx @@ -1,34 +1,20 @@ import { Button } from '@kobalte/core'; import { A } from '@solidjs/router'; -import PouchDb from 'pouchdb'; import { Component, createEffect, For } from 'solid-js'; -import { createServerAction$ } from 'solid-start/server'; import { useNavigate } from 'solid-start'; -interface Props {} +interface Props { + users: any; +} const Users: Component = (props) => { + const { users } = props; const navigate = useNavigate(); - const [users, getUsers] = createServerAction$( - async (values: any) => { - const db = new PouchDb('.db'); - const results = await db.allDocs({ - include_docs: true, - startkey: 'user/', - endkey: 'user/\ufff0', - }); - console.log({ caller: 'Users / serverAction', results }); - return results.rows; - } - ); - - getUsers(); - createEffect(() => { console.log({ caller: 'Users', - users: users.result, + users: users(), }); }); @@ -40,7 +26,7 @@ const Users: Component = (props) => { <> New
    - + {(user: any) => { console.log({ caller: 'Users / loop', @@ -48,9 +34,7 @@ const Users: Component = (props) => { }); return (
  • - - {user.doc.mail} - + {user.doc.mail}
  • ); }} diff --git a/src/lib/admin-db-exists.ts b/src/lib/admin-db-exists.ts new file mode 100644 index 0000000..4610abf --- /dev/null +++ b/src/lib/admin-db-exists.ts @@ -0,0 +1,27 @@ +import { adminCredentials } from '~/components/credentials'; +import { headersWithAuth } from './headers-with-auth'; + +export const adminDbExists = async () => { + const credentials = adminCredentials(); + if (!credentials) { + return null; + } + const { database } = credentials; + + const headers = headersWithAuth(); + if (!headers) { + return null; + } + + const response = await fetch(`${database}/_dyomedea_users`, { + method: 'HEAD', + mode: 'cors', + headers, + }); + + if (response.status !== 200) { + console.error({ caller: 'dbExists', response }); + } + + return response.status === 200; +}; diff --git a/src/lib/create-admin-db.ts b/src/lib/create-admin-db.ts new file mode 100644 index 0000000..46a4f89 --- /dev/null +++ b/src/lib/create-admin-db.ts @@ -0,0 +1,29 @@ +import { adminCredentials } from '~/components/credentials'; +import { headersWithAuth } from './headers-with-auth'; + +export const createAdminDb = async () => { + const credentials = adminCredentials(); + if (!credentials) { + return null; + } + const { database } = credentials; + + const headers = headersWithAuth(); + if (!headers) { + return null; + } + + const response = await fetch(`${database}/dyomedea_users`, { + method: 'PUT', + mode: 'cors', + headers, + }); + + console.log({ caller: 'dbExists', response }); + + if (![200, 201].includes(response.status)) { + console.error({ caller: 'dbExists', response }); + } + + return true; +}; diff --git a/src/lib/db.ts b/src/lib/db.ts deleted file mode 100644 index 1796b33..0000000 --- a/src/lib/db.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const put = async (params: { db: any; doc: any }) => { - const { db, doc } = params; - try { - const previous = await db.get(doc._id); - doc._rev = previous._rev; - } catch (error) { - console.error({ caller: 'put', doc, error }); - } - console.log({ caller: 'put', doc }); - db.put(doc); -}; diff --git a/src/lib/del.ts b/src/lib/del.ts new file mode 100644 index 0000000..9cdaa45 --- /dev/null +++ b/src/lib/del.ts @@ -0,0 +1,6 @@ +import { put } from './put'; + +export const del = async (id: string) => { + const content = { _deleted: true }; + return await put(id, content, false); +}; diff --git a/src/lib/get-url.ts b/src/lib/get-url.ts new file mode 100644 index 0000000..c6b267c --- /dev/null +++ b/src/lib/get-url.ts @@ -0,0 +1,10 @@ +import { adminCredentials } from '~/components/credentials'; + +export const getUrl = (id: string) => { + const credentials = adminCredentials(); + if (!credentials) { + return ''; + } + const { database } = credentials; + return `${database}/dyomedea_users/${id}`; +}; diff --git a/src/lib/get.ts b/src/lib/get.ts new file mode 100644 index 0000000..65bb728 --- /dev/null +++ b/src/lib/get.ts @@ -0,0 +1,23 @@ +import { adminCredentials } from '~/components/credentials'; +import { headersWithAuth } from './headers-with-auth'; + +export const get = async (id: string) => { + const credentials = adminCredentials(); + if (!credentials) { + return null; + } + const { database } = credentials; + + const headers = headersWithAuth(); + if (!headers) { + return null; + } + + const response = await fetch(`${database}/dyomedea_users/${id}`, { + method: 'GET', + mode: 'cors', + headers, + }); + + return await response.json(); +}; diff --git a/src/lib/headers-with-auth.ts b/src/lib/headers-with-auth.ts new file mode 100644 index 0000000..9078e19 --- /dev/null +++ b/src/lib/headers-with-auth.ts @@ -0,0 +1,13 @@ +import { adminCredentials } from '~/components/credentials'; + +export const headersWithAuth = () => { + const credentials = adminCredentials(); + if (!credentials) { + return null; + } + const { username, password } = credentials; + const authString = `${username}:${password}`; + const headers = new Headers(); + headers.set('Authorization', 'Basic ' + btoa(authString)); + return headers; +}; diff --git a/src/lib/put.ts b/src/lib/put.ts new file mode 100644 index 0000000..a06aca3 --- /dev/null +++ b/src/lib/put.ts @@ -0,0 +1,32 @@ +import { adminCredentials } from '~/components/credentials'; +import { get } from './get'; +import { getUrl } from './get-url'; +import { headersWithAuth } from './headers-with-auth'; + +export const put = async (id: string, content: any, isNew: boolean = false) => { + const credentials = adminCredentials(); + if (!credentials) { + return null; + } + const { database } = credentials; + + if (!isNew) { + const previous = await get(id); + content._rev = previous._rev; + } + + const headers = headersWithAuth(); + if (!headers) { + return null; + } + headers.set('Content-type', 'application/json; charset=UTF-8'); + + const response = await fetch(getUrl(id), { + method: 'PUT', + mode: 'cors', + headers, + body: JSON.stringify(content), + }); + + return await response.json(); +}; diff --git a/src/routes/user/[id].tsx b/src/routes/user/[id].tsx index 3576d8a..0d58370 100644 --- a/src/routes/user/[id].tsx +++ b/src/routes/user/[id].tsx @@ -1,36 +1,30 @@ import { useParams } from 'solid-start'; -import { createServerAction$ } from 'solid-start/server'; -import PouchDb from 'pouchdb'; import User from '~/components/user'; -import { createEffect, Show } from 'solid-js'; +import { createEffect, createSignal } from 'solid-js'; +import { adminCredentials, CheckCredentials } from '~/components/credentials'; +import { get } from '~/lib/get'; export default () => { - const params = useParams(); - const [user, getUser] = createServerAction$( - async (id: string) => { - const db = new PouchDb('.db'); - const result = await db.get(id); - console.log({ caller: 'Users / serverAction', result }); - return result; + const { id } = useParams(); + const [user, setUser] = createSignal(); + + createEffect(async () => { + if (!user()) { + setUser(await get(id)); + console.log({ + caller: '/routes/user/[id].ts / createEffect', + user: user(), + adminCredentials: adminCredentials(), + }); } - ); - - getUser(decodeURIComponent(params.id)); - - createEffect(() => { - console.log({ - caller: 'Users/[id]', - params, - user: user.result, - }); }); return (

    User

    - Loading...}> - - + + +
    ); }; diff --git a/src/routes/user/index.tsx b/src/routes/user/index.tsx index 2c1616f..4a7403b 100644 --- a/src/routes/user/index.tsx +++ b/src/routes/user/index.tsx @@ -1,21 +1,64 @@ -import { createEffect, Show } from 'solid-js'; -import Credentials, { adminCredentials } from '~/components/credentials'; +import { createEffect } from 'solid-js'; +import { createRouteData, refetchRouteData, useRouteData } from 'solid-start'; +import { adminCredentials, CheckCredentials } from '~/components/credentials'; import Users from '~/components/users'; +import { adminDbExists } from '~/lib/admin-db-exists'; +import { createAdminDb } from '~/lib/create-admin-db'; +import { headersWithAuth } from '~/lib/headers-with-auth'; + +export function routeData() { + return createRouteData(async () => { + if (!(await adminDbExists())) { + await createAdminDb(); + } + + const credentials = adminCredentials(); + if (!credentials) { + return null; + } + const { database } = credentials; + const headers = headersWithAuth(); + if (!headers) { + return null; + } + headers.set('Content-type', 'application/json; charset=UTF-8'); + const response = await fetch(`${database}/dyomedea_users/_all_docs`, { + method: 'POST', + mode: 'cors', + headers, + body: JSON.stringify({ + include_docs: true, + startkey: 'user:', + endkey: 'user:\ufff0', + }), + }); + + const results = await response.json(); + console.log({ caller: 'user route / routeData', results }); + return results.rows; + }); +} createEffect(() => { - console.log({ - caller: 'user/index / createEffect', - adminCredentials: adminCredentials(), - }); + const credentials = adminCredentials(); + if (!!credentials) { + console.log({ + caller: 'routes/user/index.tsx / createEffect', + adminCredentials: credentials, + }); + refetchRouteData(); + } }); export default () => { + const users = useRouteData(); + // const users = routeData(); return (

    Users

    - }> - - + + +
    ); }; diff --git a/src/routes/user/new.tsx b/src/routes/user/new.tsx index 59524b9..c1a67ed 100644 --- a/src/routes/user/new.tsx +++ b/src/routes/user/new.tsx @@ -1,10 +1,13 @@ -import User from "~/components/user"; +import { CheckCredentials } from '~/components/credentials'; +import User from '~/components/user'; export default () => { return (

    New user

    - + + +
    ); };