2023-02-21 20:11:03 +00:00
|
|
|
import { createForm } from '@felte/solid';
|
2023-03-02 12:18:40 +00:00
|
|
|
import {
|
|
|
|
Component,
|
|
|
|
createSignal,
|
|
|
|
Index,
|
|
|
|
Match,
|
|
|
|
onMount,
|
|
|
|
Show,
|
|
|
|
Switch,
|
|
|
|
} from 'solid-js';
|
|
|
|
import { TextField, Button, Progress } from '@kobalte/core';
|
2023-02-24 17:00:40 +00:00
|
|
|
import reporter from '@felte/reporter-tippy';
|
|
|
|
import './style.css';
|
2023-03-03 08:27:44 +00:00
|
|
|
import { A, useNavigate } from 'solid-start';
|
2023-02-24 17:00:40 +00:00
|
|
|
import { adminCredentials } from '../credentials';
|
|
|
|
import { put } from '~/lib/put';
|
|
|
|
import { del } from '~/lib/del';
|
|
|
|
import { isFunction } from 'lodash';
|
2023-02-26 17:10:33 +00:00
|
|
|
import { userId } from '~/lib/user-id';
|
2023-03-01 17:13:13 +00:00
|
|
|
import { userExists } from '~/lib/user-exists';
|
2023-03-02 13:26:34 +00:00
|
|
|
import { update } from '~/lib/update';
|
2023-03-02 19:24:04 +00:00
|
|
|
import { replicationDocument } from '~/lib/replication-document';
|
|
|
|
import { get } from '~/lib/get';
|
2023-03-03 08:15:38 +00:00
|
|
|
import { v4 as uuid } from 'uuid';
|
2023-02-21 10:23:35 +00:00
|
|
|
|
2023-02-22 10:19:10 +00:00
|
|
|
interface Props {
|
2023-02-24 17:00:40 +00:00
|
|
|
values?: () => any;
|
2023-02-22 10:19:10 +00:00
|
|
|
}
|
2023-02-21 10:23:35 +00:00
|
|
|
|
2023-02-22 20:18:57 +00:00
|
|
|
const User: Component<Props> = (props) => {
|
2023-02-22 10:19:10 +00:00
|
|
|
const navigate = useNavigate();
|
|
|
|
|
2023-03-02 12:18:40 +00:00
|
|
|
const [progress, setProgress] = createSignal(-1);
|
|
|
|
|
2023-03-02 16:29:13 +00:00
|
|
|
const defaultValues = () => ({});
|
|
|
|
const values = props.values ?? defaultValues;
|
|
|
|
const [previousSubscriptions, setPreviousSubscriptions] = createSignal(
|
|
|
|
values().subscriptions ?? []
|
|
|
|
);
|
|
|
|
|
2023-02-27 18:15:43 +00:00
|
|
|
const credentials = adminCredentials();
|
|
|
|
const { database } = credentials || { database: null };
|
|
|
|
|
|
|
|
const isNew = () => !isFunction(props?.values);
|
|
|
|
|
2023-02-24 17:00:40 +00:00
|
|
|
const getValues = () => {
|
2023-02-27 18:15:43 +00:00
|
|
|
if (isNew()) {
|
2023-03-03 08:15:38 +00:00
|
|
|
return { database, subscriptions: [], token: uuid() };
|
2023-02-22 14:32:02 +00:00
|
|
|
}
|
2023-03-03 08:15:38 +00:00
|
|
|
return { token: uuid(), subscriptions: [], ...values() };
|
2023-02-24 17:00:40 +00:00
|
|
|
};
|
|
|
|
|
2023-03-05 09:04:18 +00:00
|
|
|
const invitationLink = (type = 'https') => {
|
2023-03-03 08:27:44 +00:00
|
|
|
// TODO: make these URLs configurable
|
2023-03-05 09:04:18 +00:00
|
|
|
if (type === 'https') {
|
|
|
|
const configUrl = `https://admin.dyomedea.app/api/conf/${
|
|
|
|
getValues().token
|
|
|
|
}`;
|
|
|
|
return `https://web.dyomedea.app#${btoa(configUrl)}`;
|
|
|
|
} else {
|
|
|
|
const configUrl = `https://admin.dyomedea.app/api/conf/${
|
|
|
|
getValues().token
|
|
|
|
}`;
|
|
|
|
return `geo:?invitation=${btoa(configUrl)}`;
|
|
|
|
}
|
2023-03-03 08:27:44 +00:00
|
|
|
};
|
|
|
|
|
2023-02-22 10:19:10 +00:00
|
|
|
const submitHandler = async (values: any, context: any) => {
|
2023-02-21 10:23:35 +00:00
|
|
|
console.log({
|
2023-02-22 20:18:57 +00:00
|
|
|
caller: 'User / submitHandler',
|
2023-02-21 10:23:35 +00:00
|
|
|
props,
|
|
|
|
values,
|
|
|
|
context,
|
|
|
|
});
|
2023-03-02 13:26:34 +00:00
|
|
|
if (!database) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const { username } = values;
|
|
|
|
|
2023-03-02 12:18:40 +00:00
|
|
|
setProgress(0);
|
2023-03-02 13:26:34 +00:00
|
|
|
const id = getValues()?._id ?? userId(username, values.database);
|
2023-02-24 17:00:40 +00:00
|
|
|
await put(id, values, isNew());
|
2023-03-02 13:26:34 +00:00
|
|
|
const couchUserId = `org.couchdb.user:${username}`;
|
2023-02-24 20:02:10 +00:00
|
|
|
const userDoc = {
|
|
|
|
_id: couchUserId,
|
2023-03-02 13:26:34 +00:00
|
|
|
name: username,
|
2023-02-24 20:02:10 +00:00
|
|
|
password: values.password,
|
|
|
|
type: 'user',
|
|
|
|
roles: [],
|
|
|
|
};
|
|
|
|
await put(couchUserId, userDoc, isNew(), '_users');
|
2023-03-01 17:13:13 +00:00
|
|
|
|
2023-03-02 12:18:40 +00:00
|
|
|
setProgress(1);
|
|
|
|
|
2023-03-02 10:33:38 +00:00
|
|
|
const updatedSubscriptions = values.subscriptions ?? [];
|
|
|
|
|
|
|
|
const isIn = (username: string, subs: any[]) => {
|
|
|
|
for (let i = 0; i < subs.length; i++) {
|
|
|
|
let sub = subs[i];
|
|
|
|
if (username === sub.username) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2023-03-02 16:29:13 +00:00
|
|
|
console.log({
|
|
|
|
caller: 'User / submitHandler',
|
|
|
|
previousSubscriptions: previousSubscriptions(),
|
|
|
|
updatedSubscriptions,
|
|
|
|
});
|
2023-03-02 13:26:34 +00:00
|
|
|
|
2023-03-02 16:29:13 +00:00
|
|
|
const addSubscriptionFactory =
|
|
|
|
(subscriptionUserName: string) => (userDocument: any) => {
|
|
|
|
const subscriptions = userDocument.subscriptions || [];
|
|
|
|
subscriptions.push({ username, direction: '' });
|
|
|
|
userDocument.subscriptions = subscriptions;
|
|
|
|
return { username: subscriptionUserName, ...userDocument };
|
|
|
|
};
|
|
|
|
|
|
|
|
const removeSubscriptionFactory =
|
|
|
|
(subscriptionUserName: string) => (userDocument: any) => {
|
|
|
|
const subscriptions = userDocument.subscriptions || [];
|
|
|
|
userDocument.subscriptions = subscriptions.filter(
|
|
|
|
(subscription: any) => subscription.username !== username
|
|
|
|
);
|
|
|
|
return { username: subscriptionUserName, ...userDocument };
|
|
|
|
};
|
2023-03-02 15:39:17 +00:00
|
|
|
|
2023-03-02 13:26:34 +00:00
|
|
|
const defaultUserDocument = {
|
|
|
|
database,
|
|
|
|
};
|
|
|
|
|
2023-03-02 19:24:04 +00:00
|
|
|
const currentUserId = userId(data('username'), data('database'));
|
|
|
|
const credentials = adminCredentials();
|
|
|
|
const adminUser = credentials?.username || '';
|
|
|
|
|
2023-03-02 10:33:38 +00:00
|
|
|
for (let i = 0; i < updatedSubscriptions.length; i++) {
|
|
|
|
let subscription = updatedSubscriptions[i];
|
2023-03-02 16:29:13 +00:00
|
|
|
if (!isIn(subscription.username, previousSubscriptions())) {
|
2023-03-02 15:39:17 +00:00
|
|
|
await update(
|
2023-03-02 13:26:34 +00:00
|
|
|
userId(subscription.username, database),
|
2023-03-02 16:29:13 +00:00
|
|
|
addSubscriptionFactory(subscription.username),
|
2023-03-02 13:26:34 +00:00
|
|
|
defaultUserDocument
|
|
|
|
);
|
2023-03-02 19:24:04 +00:00
|
|
|
const otherUserId = userId(subscription.username, data('database'));
|
|
|
|
const otherUserPassword = (await get(otherUserId)).password;
|
|
|
|
let repDocument = replicationDocument(
|
|
|
|
currentUserId,
|
|
|
|
data('password'),
|
|
|
|
otherUserId,
|
|
|
|
otherUserPassword,
|
|
|
|
adminUser
|
|
|
|
);
|
|
|
|
await put(repDocument._id, repDocument, false, '_replicator');
|
|
|
|
repDocument = replicationDocument(
|
|
|
|
otherUserId,
|
|
|
|
otherUserPassword,
|
|
|
|
currentUserId,
|
|
|
|
data('password'),
|
|
|
|
adminUser
|
|
|
|
);
|
|
|
|
await put(repDocument._id, repDocument, false, '_replicator');
|
2023-03-02 10:33:38 +00:00
|
|
|
console.log({
|
|
|
|
caller: 'User / submitHandler / new subscription',
|
|
|
|
username: subscription.username,
|
|
|
|
});
|
|
|
|
}
|
2023-02-22 10:19:10 +00:00
|
|
|
}
|
2023-03-02 10:33:38 +00:00
|
|
|
|
2023-03-02 12:18:40 +00:00
|
|
|
setProgress(2);
|
|
|
|
|
2023-03-02 16:29:13 +00:00
|
|
|
for (let i = 0; i < previousSubscriptions().length; i++) {
|
|
|
|
let subscription = previousSubscriptions()[i];
|
2023-03-02 10:33:38 +00:00
|
|
|
if (!isIn(subscription.username, updatedSubscriptions)) {
|
2023-03-02 15:39:17 +00:00
|
|
|
await update(
|
|
|
|
userId(subscription.username, database),
|
2023-03-02 16:29:13 +00:00
|
|
|
removeSubscriptionFactory(subscription.username),
|
2023-03-02 15:39:17 +00:00
|
|
|
defaultUserDocument
|
|
|
|
);
|
2023-03-02 19:24:04 +00:00
|
|
|
|
2023-03-02 19:30:59 +00:00
|
|
|
const otherUserId = userId(subscription.username, data('database'));
|
|
|
|
await del(`${currentUserId}=>${otherUserId}`, '_replicator');
|
|
|
|
await del(`${otherUserId}=>${currentUserId}`, '_replicator');
|
|
|
|
|
2023-03-02 10:33:38 +00:00
|
|
|
console.log({
|
|
|
|
caller: 'User / submitHandler / deleted subscription',
|
|
|
|
username: subscription.username,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2023-03-02 12:18:40 +00:00
|
|
|
setProgress(3);
|
2023-03-02 10:33:38 +00:00
|
|
|
|
|
|
|
setInitialValues(values);
|
2023-03-02 16:29:13 +00:00
|
|
|
setPreviousSubscriptions(values.subscriptions);
|
2023-03-02 10:33:38 +00:00
|
|
|
setIsDirty(false);
|
|
|
|
|
2023-03-02 12:18:40 +00:00
|
|
|
setProgress(-1);
|
2023-03-02 10:33:38 +00:00
|
|
|
navigate(`/user/${id}`);
|
2023-02-21 10:23:35 +00:00
|
|
|
};
|
|
|
|
|
2023-02-22 10:58:56 +00:00
|
|
|
const cancelHandler = () => {
|
2023-02-22 20:18:57 +00:00
|
|
|
navigate(`/user/`);
|
2023-02-22 10:58:56 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const deleteHandler = async () => {
|
|
|
|
console.log({
|
2023-02-22 20:18:57 +00:00
|
|
|
caller: 'User / deleteHandler',
|
2023-02-24 17:00:40 +00:00
|
|
|
id: getValues()?._id,
|
2023-02-22 10:58:56 +00:00
|
|
|
props,
|
|
|
|
});
|
2023-02-24 17:00:40 +00:00
|
|
|
await del(getValues()?._id);
|
2023-02-22 20:18:57 +00:00
|
|
|
navigate(`/user/`);
|
2023-02-22 10:58:56 +00:00
|
|
|
};
|
|
|
|
|
2023-02-24 17:00:40 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-03-01 17:13:13 +00:00
|
|
|
const subscriptions = values.subscriptions ?? [];
|
|
|
|
|
|
|
|
for (let i = 0; i < subscriptions.length; i++) {
|
|
|
|
let subscription = subscriptions[i];
|
|
|
|
if (
|
|
|
|
!!subscription.username &&
|
|
|
|
!(await userExists(subscription.username))
|
|
|
|
) {
|
|
|
|
if (!errors.subscriptions) {
|
|
|
|
errors.subscriptions = [];
|
2023-02-24 17:00:40 +00:00
|
|
|
}
|
2023-03-01 17:13:13 +00:00
|
|
|
errors.subscriptions[i] = { username: "User doesn't exist" };
|
2023-02-24 17:00:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-01 17:13:13 +00:00
|
|
|
if (isNew()) {
|
|
|
|
if (await userExists(values.username)) {
|
|
|
|
errors.username = 'The user is already existing';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
console.log({ caller: 'Users / validationHandler', values, errors });
|
2023-02-24 17:00:40 +00:00
|
|
|
return errors;
|
|
|
|
};
|
|
|
|
|
2023-03-01 17:13:13 +00:00
|
|
|
const {
|
|
|
|
form,
|
|
|
|
data,
|
|
|
|
setData,
|
2023-03-02 10:33:38 +00:00
|
|
|
setIsDirty,
|
2023-03-01 17:13:13 +00:00
|
|
|
setInitialValues,
|
|
|
|
reset,
|
|
|
|
addField,
|
|
|
|
unsetField,
|
|
|
|
isDirty,
|
|
|
|
isValid,
|
|
|
|
} = createForm({
|
|
|
|
onSubmit: submitHandler,
|
|
|
|
extend: reporter(),
|
|
|
|
validate: validationHandler,
|
|
|
|
initialValues: getValues(),
|
|
|
|
});
|
2023-02-22 13:54:41 +00:00
|
|
|
|
2023-02-22 14:45:52 +00:00
|
|
|
const createUserHandler = async () => {
|
|
|
|
console.log({
|
2023-02-22 20:18:57 +00:00
|
|
|
caller: 'User / createUserHandler',
|
2023-02-22 14:45:52 +00:00
|
|
|
props,
|
|
|
|
data: data(),
|
|
|
|
});
|
2023-02-24 17:00:40 +00:00
|
|
|
// setOpenUserNamePasswordDialog(true);
|
2023-02-22 17:24:06 +00:00
|
|
|
};
|
|
|
|
|
2023-02-27 18:15:43 +00:00
|
|
|
const subscriptions = () => data('subscriptions');
|
|
|
|
|
|
|
|
function removeSubscription(index: number) {
|
2023-03-02 10:33:38 +00:00
|
|
|
return () => {
|
|
|
|
unsetField(`subscriptions.${index}`);
|
|
|
|
setIsDirty(true);
|
|
|
|
};
|
2023-02-27 18:15:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function addSubscription(index?: number) {
|
2023-03-02 10:33:38 +00:00
|
|
|
return () => {
|
2023-02-27 18:28:18 +00:00
|
|
|
addField(`subscriptions`, { username: '', direction: '' }, index);
|
2023-03-02 10:33:38 +00:00
|
|
|
setIsDirty(true);
|
|
|
|
};
|
2023-02-27 18:15:43 +00:00
|
|
|
}
|
|
|
|
|
2023-02-22 10:19:10 +00:00
|
|
|
console.log({
|
2023-02-22 20:18:57 +00:00
|
|
|
caller: 'User ',
|
2023-02-22 10:19:10 +00:00
|
|
|
props,
|
2023-02-24 17:00:40 +00:00
|
|
|
values: getValues(),
|
2023-02-27 18:15:43 +00:00
|
|
|
data: data(),
|
2023-02-22 10:19:10 +00:00
|
|
|
});
|
2023-02-21 10:23:35 +00:00
|
|
|
|
|
|
|
return (
|
2023-02-27 18:28:18 +00:00
|
|
|
<>
|
2023-03-02 12:18:40 +00:00
|
|
|
<Show when={progress() >= 0}>
|
|
|
|
<Progress.Root
|
|
|
|
value={progress()}
|
|
|
|
minValue={0}
|
2023-03-02 19:24:04 +00:00
|
|
|
maxValue={3}
|
2023-03-02 12:18:40 +00:00
|
|
|
getValueLabel={({ value, max }) =>
|
|
|
|
`${value} of ${max} tasks completed`
|
|
|
|
}
|
|
|
|
class='progress'
|
|
|
|
>
|
|
|
|
<div class='progress__label-container'>
|
|
|
|
<Progress.Label class='progress__label'>
|
|
|
|
Processing...
|
|
|
|
</Progress.Label>
|
|
|
|
<Progress.ValueLabel class='progress__value-label' />
|
|
|
|
</div>
|
|
|
|
<Progress.Track class='progress__track'>
|
|
|
|
<Progress.Fill class='progress__fill' />
|
|
|
|
</Progress.Track>
|
|
|
|
</Progress.Root>
|
|
|
|
</Show>
|
|
|
|
<Show when={progress() < 0}>
|
|
|
|
<Show when={!isNew()}>
|
|
|
|
<h2>{userId(data('username'), data('database'))}</h2>
|
|
|
|
</Show>
|
2023-03-03 08:27:44 +00:00
|
|
|
<h3>
|
2023-03-05 09:04:18 +00:00
|
|
|
Invitations :
|
2023-03-03 08:27:44 +00:00
|
|
|
<A href={invitationLink()} target='_blank'>
|
2023-03-05 09:04:18 +00:00
|
|
|
https
|
|
|
|
</A>
|
|
|
|
<A href={invitationLink('geo')} target='_blank'>
|
|
|
|
geo
|
2023-03-03 08:27:44 +00:00
|
|
|
</A>
|
|
|
|
</h3>
|
2023-03-02 12:18:40 +00:00
|
|
|
<form use:form>
|
|
|
|
<TextField.Root>
|
|
|
|
<TextField.Label>Mail address</TextField.Label>
|
|
|
|
<TextField.Input
|
|
|
|
type='mail'
|
|
|
|
name='mail'
|
|
|
|
required={true}
|
|
|
|
placeholder='Email address'
|
|
|
|
/>
|
|
|
|
<TextField.ErrorMessage>
|
|
|
|
Please provide a valid URL
|
|
|
|
</TextField.ErrorMessage>
|
|
|
|
</TextField.Root>
|
|
|
|
<TextField.Root>
|
|
|
|
<TextField.Label>Database</TextField.Label>
|
|
|
|
<TextField.Input
|
|
|
|
type='url'
|
|
|
|
name='database'
|
|
|
|
required={true}
|
|
|
|
placeholder='Database URL'
|
|
|
|
readOnly={true}
|
|
|
|
/>
|
|
|
|
</TextField.Root>
|
|
|
|
<TextField.Root>
|
|
|
|
<TextField.Label>User name</TextField.Label>
|
|
|
|
<TextField.Input
|
|
|
|
type='text'
|
|
|
|
name='username'
|
|
|
|
required={true}
|
|
|
|
placeholder='user name'
|
|
|
|
readOnly={!isNew()}
|
|
|
|
/>
|
|
|
|
</TextField.Root>
|
|
|
|
<TextField.Root>
|
|
|
|
<TextField.Label>Password</TextField.Label>
|
|
|
|
<TextField.Input
|
|
|
|
type='text'
|
|
|
|
name='password'
|
|
|
|
required={true}
|
|
|
|
placeholder='Password'
|
|
|
|
autocomplete='off'
|
|
|
|
/>
|
|
|
|
</TextField.Root>
|
|
|
|
<table>
|
|
|
|
<thead>
|
|
|
|
<tr>
|
|
|
|
<th>
|
|
|
|
<button type='button' onClick={addSubscription()}>
|
|
|
|
+
|
|
|
|
</button>
|
|
|
|
</th>
|
|
|
|
<th>User name</th>
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody>
|
|
|
|
<Index each={subscriptions()}>
|
|
|
|
{(_, index) => (
|
|
|
|
<tr>
|
|
|
|
<td>
|
|
|
|
<button type='button' onClick={removeSubscription(index)}>
|
|
|
|
-
|
|
|
|
</button>
|
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
<TextField.Root>
|
|
|
|
<TextField.Input
|
|
|
|
type='text'
|
|
|
|
name={`subscriptions.${index}.username`}
|
|
|
|
required={true}
|
|
|
|
placeholder='user name'
|
|
|
|
/>
|
|
|
|
</TextField.Root>
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
)}
|
|
|
|
</Index>
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
<Switch>
|
|
|
|
<Match when={!isNew()}>
|
|
|
|
<Button.Root type='submit' disabled={!isDirty() || !isValid()}>
|
|
|
|
Save
|
|
|
|
</Button.Root>
|
|
|
|
<Button.Root onclick={deleteHandler}>Delete</Button.Root>
|
|
|
|
</Match>
|
|
|
|
<Match when={isNew()}>
|
|
|
|
<Button.Root type='submit' isDisabled={!isValid()}>
|
|
|
|
Create
|
|
|
|
</Button.Root>
|
|
|
|
</Match>
|
|
|
|
</Switch>
|
|
|
|
<Button.Root onclick={cancelHandler}>Back</Button.Root>
|
|
|
|
</form>
|
2023-02-27 18:28:18 +00:00
|
|
|
</Show>
|
|
|
|
</>
|
2023-02-21 10:23:35 +00:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2023-02-22 20:18:57 +00:00
|
|
|
export default User;
|