implemented the user view type and website sharing
parent
7a3443cd06
commit
66e67c7a3b
|
@ -1,18 +1,23 @@
|
||||||
import React, { useRef } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Icon from 'components/common/Icon';
|
import Icon from 'components/common/Icon';
|
||||||
import Check from 'assets/check.svg';
|
import Check from 'assets/check.svg';
|
||||||
import styles from './Checkbox.module.css';
|
import styles from './Checkbox.module.css';
|
||||||
|
|
||||||
function Checkbox({ name, value, label, onChange }) {
|
function Checkbox({ name, value, label, onChange, valueArray }) {
|
||||||
const ref = useRef();
|
const ref = useRef();
|
||||||
|
const [isChecked, setIsChecked] = useState();
|
||||||
|
|
||||||
const onClick = () => ref.current.click();
|
const onClick = () => ref.current.click();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsChecked((valueArray && valueArray.includes(value)) || (!valueArray && value));
|
||||||
|
}, [valueArray, value]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<div className={styles.checkbox} onClick={onClick}>
|
<div className={styles.checkbox} onClick={onClick}>
|
||||||
{value && <Icon icon={<Check />} size="small" />}
|
{isChecked && <Icon icon={<Check />} size="small" />}
|
||||||
</div>
|
</div>
|
||||||
<label className={styles.label} htmlFor={name} onClick={onClick}>
|
<label className={styles.label} htmlFor={name} onClick={onClick}>
|
||||||
{label}
|
{label}
|
||||||
|
@ -22,7 +27,8 @@ function Checkbox({ name, value, label, onChange }) {
|
||||||
className={styles.input}
|
className={styles.input}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name={name}
|
name={name}
|
||||||
defaultChecked={value}
|
value={valueArray ? value : isChecked}
|
||||||
|
defaultChecked={isChecked}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import React, { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { Formik, Form, Field } from 'formik';
|
import { Formik, Form, Field, useFormikContext, useField } from 'formik';
|
||||||
import Button from 'components/common/Button';
|
import Button from 'components/common/Button';
|
||||||
|
import Checkbox from 'components/common/Checkbox';
|
||||||
import FormLayout, {
|
import FormLayout, {
|
||||||
FormButtons,
|
FormButtons,
|
||||||
FormError,
|
FormError,
|
||||||
|
@ -9,10 +10,13 @@ import FormLayout, {
|
||||||
FormRow,
|
FormRow,
|
||||||
} from 'components/layout/FormLayout';
|
} from 'components/layout/FormLayout';
|
||||||
import useApi from 'hooks/useApi';
|
import useApi from 'hooks/useApi';
|
||||||
|
import useFetch from 'hooks/useFetch';
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
|
isViewer: false,
|
||||||
|
websiteIds: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const validate = ({ id, username, password }) => {
|
const validate = ({ id, username, password }) => {
|
||||||
|
@ -28,6 +32,44 @@ const validate = ({ id, username, password }) => {
|
||||||
return errors;
|
return errors;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const WebsiteSelect = props => {
|
||||||
|
const { data } = useFetch(`/websites`);
|
||||||
|
const [field, meta] = useField(props);
|
||||||
|
const {
|
||||||
|
values: { websiteIds },
|
||||||
|
} = useFormikContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{data && data.length > 0 && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
maxHeight: '20vh',
|
||||||
|
overflowY: 'auto',
|
||||||
|
padding: '0 1rem',
|
||||||
|
margin: '0 20px',
|
||||||
|
background: 'var(--gray100)',
|
||||||
|
border: '1px solid var(--gray500)',
|
||||||
|
borderRadius: '5px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.map(item => (
|
||||||
|
<div key={`websiteIds-${item.id}`}>
|
||||||
|
<Checkbox
|
||||||
|
{...field}
|
||||||
|
value={item.id.toString()}
|
||||||
|
label={item.name}
|
||||||
|
valueArray={websiteIds}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!!meta.touched && !!meta.error && <div>{meta.error}</div>}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default function AccountEditForm({ values, onSave, onClose }) {
|
export default function AccountEditForm({ values, onSave, onClose }) {
|
||||||
const { post } = useApi();
|
const { post } = useApi();
|
||||||
const [message, setMessage] = useState();
|
const [message, setMessage] = useState();
|
||||||
|
@ -52,7 +94,7 @@ export default function AccountEditForm({ values, onSave, onClose }) {
|
||||||
validate={validate}
|
validate={validate}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
{() => (
|
{values => (
|
||||||
<Form>
|
<Form>
|
||||||
<FormRow>
|
<FormRow>
|
||||||
<label htmlFor="username">
|
<label htmlFor="username">
|
||||||
|
@ -72,6 +114,20 @@ export default function AccountEditForm({ values, onSave, onClose }) {
|
||||||
<FormError name="password" />
|
<FormError name="password" />
|
||||||
</div>
|
</div>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
|
{!values.values.isAdmin && (
|
||||||
|
<FormRow>
|
||||||
|
<label />
|
||||||
|
<Field name="isViewer">
|
||||||
|
{({ field }) => (
|
||||||
|
<Checkbox
|
||||||
|
{...field}
|
||||||
|
label={<FormattedMessage id="label.is-viewer" defaultMessage="Viewer" />}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</FormRow>
|
||||||
|
)}
|
||||||
|
{values.values.isViewer && <WebsiteSelect name="websiteIds" />}
|
||||||
<FormButtons>
|
<FormButtons>
|
||||||
<Button type="submit" variant="action">
|
<Button type="submit" variant="action">
|
||||||
<FormattedMessage id="label.save" defaultMessage="Save" />
|
<FormattedMessage id="label.save" defaultMessage="Save" />
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
@ -26,6 +26,14 @@ export default function AccountSettings() {
|
||||||
const [saved, setSaved] = useState(0);
|
const [saved, setSaved] = useState(0);
|
||||||
const [message, setMessage] = useState();
|
const [message, setMessage] = useState();
|
||||||
const { data } = useFetch(`/accounts`, {}, [saved]);
|
const { data } = useFetch(`/accounts`, {}, [saved]);
|
||||||
|
const [formattedData, setFormattedData] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data)
|
||||||
|
setFormattedData(
|
||||||
|
data.map(d => ({ ...d, websiteIds: d.viewwebsites.map(vw => vw.websiteId.toString()) })),
|
||||||
|
);
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
const Checkmark = ({ isAdmin }) => (isAdmin ? <Icon icon={<Check />} size="medium" /> : null);
|
const Checkmark = ({ isAdmin }) => (isAdmin ? <Icon icon={<Check />} size="medium" /> : null);
|
||||||
|
|
||||||
|
@ -103,7 +111,7 @@ export default function AccountSettings() {
|
||||||
<FormattedMessage id="label.add-account" defaultMessage="Add account" />
|
<FormattedMessage id="label.add-account" defaultMessage="Add account" />
|
||||||
</Button>
|
</Button>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<Table columns={columns} rows={data} />
|
<Table columns={columns} rows={formattedData} />
|
||||||
{editAccount && (
|
{editAccount && (
|
||||||
<Modal title={<FormattedMessage id="label.edit-account" defaultMessage="Edit account" />}>
|
<Modal title={<FormattedMessage id="label.edit-account" defaultMessage="Edit account" />}>
|
||||||
<AccountEditForm
|
<AccountEditForm
|
||||||
|
|
|
@ -50,6 +50,7 @@ export default function WebsiteSettings() {
|
||||||
onClick={() => setShowUrl(row)}
|
onClick={() => setShowUrl(row)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{!user.isViewer && (
|
||||||
<Button
|
<Button
|
||||||
icon={<Code />}
|
icon={<Code />}
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -59,6 +60,8 @@ export default function WebsiteSettings() {
|
||||||
tooltipId={`button-code-${row.websiteUuid}`}
|
tooltipId={`button-code-${row.websiteUuid}`}
|
||||||
onClick={() => setShowCode(row)}
|
onClick={() => setShowCode(row)}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
{!user.isViewer && (
|
||||||
<Button
|
<Button
|
||||||
icon={<Pen />}
|
icon={<Pen />}
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -66,6 +69,8 @@ export default function WebsiteSettings() {
|
||||||
tooltipId={`button-edit-${row.websiteUuid}`}
|
tooltipId={`button-edit-${row.websiteUuid}`}
|
||||||
onClick={() => setEditWebsite(row)}
|
onClick={() => setEditWebsite(row)}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
{!user.isViewer && (
|
||||||
<Button
|
<Button
|
||||||
icon={<Reset />}
|
icon={<Reset />}
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -73,6 +78,8 @@ export default function WebsiteSettings() {
|
||||||
tooltipId={`button-reset-${row.websiteUuid}`}
|
tooltipId={`button-reset-${row.websiteUuid}`}
|
||||||
onClick={() => setResetWebsite(row)}
|
onClick={() => setResetWebsite(row)}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
{!user.isViewer && (
|
||||||
<Button
|
<Button
|
||||||
icon={<Trash />}
|
icon={<Trash />}
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -80,6 +87,7 @@ export default function WebsiteSettings() {
|
||||||
tooltipId={`button-delete-${row.websiteUuid}`}
|
tooltipId={`button-delete-${row.websiteUuid}`}
|
||||||
onClick={() => setDeleteWebsite(row)}
|
onClick={() => setDeleteWebsite(row)}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</ButtonLayout>
|
</ButtonLayout>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `account` ADD COLUMN `is_viewer` BOOLEAN NOT NULL DEFAULT false;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `event_data` DROP PRIMARY KEY,
|
||||||
|
MODIFY `event_data_id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
ADD PRIMARY KEY (`event_data_id`);
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE `_event_old`;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `viewerforwebsite` (
|
||||||
|
`user_id` INTEGER UNSIGNED NOT NULL,
|
||||||
|
`website_id` INTEGER UNSIGNED NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (`user_id`, `website_id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `viewerforwebsite` ADD CONSTRAINT `viewerforwebsite_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `account`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `viewerforwebsite` ADD CONSTRAINT `viewerforwebsite_website_id_fkey` FOREIGN KEY (`website_id`) REFERENCES `website`(`website_id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@ -12,10 +12,12 @@ model account {
|
||||||
username String @unique() @db.VarChar(255)
|
username String @unique() @db.VarChar(255)
|
||||||
password String @db.VarChar(60)
|
password String @db.VarChar(60)
|
||||||
isAdmin Boolean @default(false) @map("is_admin")
|
isAdmin Boolean @default(false) @map("is_admin")
|
||||||
|
isViewer Boolean @default(false) @map("is_viewer")
|
||||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamp(0)
|
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamp(0)
|
||||||
updatedAt DateTime? @default(now()) @map("updated_at") @db.Timestamp(0)
|
updatedAt DateTime? @default(now()) @map("updated_at") @db.Timestamp(0)
|
||||||
accountUuid String @unique() @map("account_uuid") @db.VarChar(36)
|
accountUuid String @unique() @map("account_uuid") @db.VarChar(36)
|
||||||
website website[]
|
website website[]
|
||||||
|
viewwebsites viewerforwebsite[]
|
||||||
|
|
||||||
@@index([accountUuid])
|
@@index([accountUuid])
|
||||||
}
|
}
|
||||||
|
@ -97,7 +99,18 @@ model website {
|
||||||
event event[]
|
event event[]
|
||||||
pageview pageview[]
|
pageview pageview[]
|
||||||
session session[]
|
session session[]
|
||||||
|
viewers viewerforwebsite[]
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([websiteUuid])
|
@@index([websiteUuid])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model viewerforwebsite {
|
||||||
|
userId Int @map("user_id") @db.UnsignedInt
|
||||||
|
websiteId Int @map("website_id") @db.UnsignedInt
|
||||||
|
|
||||||
|
account account @relation(fields: [userId], references: [id])
|
||||||
|
website website @relation(fields: [websiteId], references: [id])
|
||||||
|
|
||||||
|
@@id([userId, websiteId])
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "account" ADD COLUMN "is_viewer" BOOLEAN NOT NULL DEFAULT false;
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "_event_old";
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "viewerforwebsite" (
|
||||||
|
"user_id" INTEGER NOT NULL,
|
||||||
|
"website_id" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "viewerforwebsite_pkey" PRIMARY KEY ("user_id","website_id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "viewerforwebsite" ADD CONSTRAINT "viewerforwebsite_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "account"("user_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "viewerforwebsite" ADD CONSTRAINT "viewerforwebsite_website_id_fkey" FOREIGN KEY ("website_id") REFERENCES "website"("website_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@ -12,10 +12,12 @@ model account {
|
||||||
username String @unique @db.VarChar(255)
|
username String @unique @db.VarChar(255)
|
||||||
password String @db.VarChar(60)
|
password String @db.VarChar(60)
|
||||||
isAdmin Boolean @default(false) @map("is_admin")
|
isAdmin Boolean @default(false) @map("is_admin")
|
||||||
|
isViewer Boolean @default(false) @map("is_viewer")
|
||||||
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||||
updatedAt DateTime? @default(now()) @map("updated_at") @db.Timestamptz(6)
|
updatedAt DateTime? @default(now()) @map("updated_at") @db.Timestamptz(6)
|
||||||
accountUuid String @unique @map("account_uuid") @db.Uuid
|
accountUuid String @unique @map("account_uuid") @db.Uuid
|
||||||
website website[]
|
website website[]
|
||||||
|
viewwebsites viewerforwebsite[]
|
||||||
|
|
||||||
@@index([accountUuid])
|
@@index([accountUuid])
|
||||||
}
|
}
|
||||||
|
@ -97,7 +99,18 @@ model website {
|
||||||
event event[]
|
event event[]
|
||||||
pageview pageview[]
|
pageview pageview[]
|
||||||
session session[]
|
session session[]
|
||||||
|
viewers viewerforwebsite[]
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([websiteUuid])
|
@@index([websiteUuid])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model viewerforwebsite {
|
||||||
|
userId Int @map("user_id")
|
||||||
|
websiteId Int @map("website_id")
|
||||||
|
|
||||||
|
account account @relation(fields: [userId], references: [id])
|
||||||
|
website website @relation(fields: [websiteId], references: [id])
|
||||||
|
|
||||||
|
@@id([userId, websiteId])
|
||||||
|
}
|
||||||
|
|
|
@ -52,7 +52,10 @@ export async function allowQuery(req, type, allowShareToken = true) {
|
||||||
if (type === TYPE_WEBSITE) {
|
if (type === TYPE_WEBSITE) {
|
||||||
const website = await getWebsite({ websiteUuid: id });
|
const website = await getWebsite({ websiteUuid: id });
|
||||||
|
|
||||||
return website && website.userId === userId;
|
return (
|
||||||
|
website &&
|
||||||
|
(website.userId === userId || website.viewers.map(v => v.userId).includes(userId))
|
||||||
|
);
|
||||||
} else if (type === TYPE_ACCOUNT) {
|
} else if (type === TYPE_ACCOUNT) {
|
||||||
const account = await getAccount({ accountUuid: id });
|
const account = await getAccount({ accountUuid: id });
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ export default async (req, res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
const { username, password } = req.body;
|
const { username, password, isViewer, websiteIds } = req.body;
|
||||||
|
|
||||||
if (id !== userId && !isAdmin) {
|
if (id !== userId && !isAdmin) {
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
|
@ -29,6 +29,8 @@ export default async (req, res) => {
|
||||||
|
|
||||||
const data = {};
|
const data = {};
|
||||||
|
|
||||||
|
data.isViewer = isViewer;
|
||||||
|
|
||||||
if (password) {
|
if (password) {
|
||||||
data.password = hashPassword(password);
|
data.password = hashPassword(password);
|
||||||
}
|
}
|
||||||
|
@ -36,6 +38,24 @@ export default async (req, res) => {
|
||||||
// Only admin can change these fields
|
// Only admin can change these fields
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
data.username = username;
|
data.username = username;
|
||||||
|
|
||||||
|
const existingWebsiteIds = websiteIds.map(id => parseInt(id));
|
||||||
|
|
||||||
|
const websitesToDisconnect = account.viewwebsites
|
||||||
|
.map(vw => vw.websiteId)
|
||||||
|
.filter(id => !existingWebsiteIds.includes(id));
|
||||||
|
|
||||||
|
const websitesToConnect = existingWebsiteIds.filter(
|
||||||
|
id => !account.viewwebsites.map(vw => vw.websiteId).includes(id),
|
||||||
|
);
|
||||||
|
|
||||||
|
data.viewwebsites = {
|
||||||
|
create: websitesToConnect.map(id => ({ website: { connect: { id } } })),
|
||||||
|
deleteMany: websitesToDisconnect.map(id => ({
|
||||||
|
websiteId: id,
|
||||||
|
userId: account.id,
|
||||||
|
})),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check when username changes
|
// Check when username changes
|
||||||
|
|
|
@ -19,7 +19,7 @@ export default async (req, res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
const { username, password, account_uuid } = req.body;
|
const { username, password, account_uuid, websiteIds, isViewer } = req.body;
|
||||||
|
|
||||||
const account = await getAccount({ username });
|
const account = await getAccount({ username });
|
||||||
|
|
||||||
|
@ -31,6 +31,14 @@ export default async (req, res) => {
|
||||||
username,
|
username,
|
||||||
password: hashPassword(password),
|
password: hashPassword(password),
|
||||||
accountUuid: account_uuid || uuid(),
|
accountUuid: account_uuid || uuid(),
|
||||||
|
isViewer,
|
||||||
|
viewwebsites: {
|
||||||
|
create: websiteIds
|
||||||
|
.map(id => parseInt(id))
|
||||||
|
.map(id => ({
|
||||||
|
website: { connect: { id } },
|
||||||
|
})),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return ok(res, created);
|
return ok(res, created);
|
||||||
|
|
|
@ -12,8 +12,8 @@ export default async (req, res) => {
|
||||||
const account = await getAccount({ username });
|
const account = await getAccount({ username });
|
||||||
|
|
||||||
if (account && checkPassword(password, account.password)) {
|
if (account && checkPassword(password, account.password)) {
|
||||||
const { id, username, isAdmin, accountUuid } = account;
|
const { id, username, isAdmin, isViewer, accountUuid } = account;
|
||||||
const user = { userId: id, username, isAdmin, accountUuid };
|
const user = { userId: id, username, isAdmin, isViewer, accountUuid };
|
||||||
const token = createSecureToken(user, secret());
|
const token = createSecureToken(user, secret());
|
||||||
|
|
||||||
return ok(res, { token, user });
|
return ok(res, { token, user });
|
||||||
|
|
|
@ -14,7 +14,9 @@ export default async (req, res) => {
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
const websites = await getUserWebsites({ userId });
|
const websites = await getUserWebsites({
|
||||||
|
OR: [{ userId }, { viewers: { some: { userId } } }],
|
||||||
|
});
|
||||||
const ids = websites.map(({ websiteUuid }) => websiteUuid);
|
const ids = websites.map(({ websiteUuid }) => websiteUuid);
|
||||||
const token = createToken({ websites: ids }, secret());
|
const token = createToken({ websites: ids }, secret());
|
||||||
const data = await getRealtimeData(ids, subMinutes(new Date(), 30));
|
const data = await getRealtimeData(ids, subMinutes(new Date(), 30));
|
||||||
|
|
|
@ -9,6 +9,7 @@ export default async (req, res) => {
|
||||||
await useAuth(req, res);
|
await useAuth(req, res);
|
||||||
|
|
||||||
const { id: websiteUuid } = req.query;
|
const { id: websiteUuid } = req.query;
|
||||||
|
const { accountUuid, isViewer } = req.auth;
|
||||||
|
|
||||||
if (req.method === 'GET') {
|
if (req.method === 'GET') {
|
||||||
if (!(await allowQuery(req, TYPE_WEBSITE))) {
|
if (!(await allowQuery(req, TYPE_WEBSITE))) {
|
||||||
|
@ -21,12 +22,11 @@ export default async (req, res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
if (!(await allowQuery(req, TYPE_WEBSITE, false))) {
|
if (!(await allowQuery(req, TYPE_WEBSITE, false)) || isViewer) {
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, domain, owner, enableShareUrl, shareId } = req.body;
|
const { name, domain, owner, enableShareUrl, shareId } = req.body;
|
||||||
const { accountUuid } = req.auth;
|
|
||||||
|
|
||||||
let account;
|
let account;
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ export default async (req, res) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.method === 'DELETE') {
|
if (req.method === 'DELETE') {
|
||||||
if (!(await allowQuery(req, TYPE_WEBSITE, false))) {
|
if (!(await allowQuery(req, TYPE_WEBSITE, false)) || isViewer) {
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,10 @@ export default async (req, res) => {
|
||||||
await useAuth(req, res);
|
await useAuth(req, res);
|
||||||
|
|
||||||
const { id: websiteId } = req.query;
|
const { id: websiteId } = req.query;
|
||||||
|
const { isViewer } = req.auth;
|
||||||
|
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
if (!(await allowQuery(req, TYPE_WEBSITE, false))) {
|
if (!(await allowQuery(req, TYPE_WEBSITE, false)) || isViewer) {
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,8 @@ export default async (req, res) => {
|
||||||
|
|
||||||
const { user_id, include_all } = req.query;
|
const { user_id, include_all } = req.query;
|
||||||
|
|
||||||
const { userId: currentUserId, isAdmin } = req.auth;
|
const { userId: currentUserId, isAdmin, isViewer } = req.auth;
|
||||||
|
|
||||||
const accountUuid = user_id || req.auth.accountUuid;
|
const accountUuid = user_id || req.auth.accountUuid;
|
||||||
let account;
|
let account;
|
||||||
|
|
||||||
|
@ -26,7 +27,9 @@ export default async (req, res) => {
|
||||||
const websites =
|
const websites =
|
||||||
isAdmin && include_all
|
isAdmin && include_all
|
||||||
? await getAllWebsites()
|
? await getAllWebsites()
|
||||||
: await getUserWebsites({ userId: account?.id });
|
: await getUserWebsites({
|
||||||
|
OR: [{ userId: account?.id }, { viewers: { some: { userId: account?.id } } }],
|
||||||
|
});
|
||||||
|
|
||||||
return ok(res, websites);
|
return ok(res, websites);
|
||||||
}
|
}
|
||||||
|
@ -36,7 +39,7 @@ export default async (req, res) => {
|
||||||
|
|
||||||
const website_owner = account ? account.id : +owner;
|
const website_owner = account ? account.id : +owner;
|
||||||
|
|
||||||
if (website_owner !== currentUserId && !isAdmin) {
|
if ((website_owner !== currentUserId && !isAdmin) || isViewer) {
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,5 +3,8 @@ import prisma from 'lib/prisma';
|
||||||
export async function getAccount(where) {
|
export async function getAccount(where) {
|
||||||
return prisma.client.account.findUnique({
|
return prisma.client.account.findUnique({
|
||||||
where,
|
where,
|
||||||
|
include: {
|
||||||
|
viewwebsites: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,11 @@ export async function getAccounts() {
|
||||||
id: true,
|
id: true,
|
||||||
username: true,
|
username: true,
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
|
isViewer: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
updatedAt: true,
|
updatedAt: true,
|
||||||
accountUuid: true,
|
accountUuid: true,
|
||||||
|
viewwebsites: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,9 @@ export async function getWebsite(where) {
|
||||||
return prisma.client.website
|
return prisma.client.website
|
||||||
.findUnique({
|
.findUnique({
|
||||||
where,
|
where,
|
||||||
|
include: {
|
||||||
|
viewers: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.then(async data => {
|
.then(async data => {
|
||||||
if (redis.enabled && data) {
|
if (redis.enabled && data) {
|
||||||
|
|
Loading…
Reference in New Issue