feat: add delete account section to settings

This commit is contained in:
poeti8 2020-02-15 16:57:57 +03:30
parent 8f58452f4c
commit ed4d528c8d
6 changed files with 96 additions and 18 deletions

View File

@ -0,0 +1,72 @@
import { useFormState } from "react-use-form-state";
import React, { FC, useState } from "react";
import Router from "next/router";
import axios from "axios";
import { getAxiosConfig } from "../../utils";
import { Col, RowCenterV } from "../Layout";
import Text, { H2, Span } from "../Text";
import { useMessage } from "../../hooks";
import { TextInput } from "../Input";
import { APIv2 } from "../../consts";
import { Button } from "../Button";
import Icon from "../Icon";
const SettingsDeleteAccount: FC = () => {
const [message, setMessage] = useMessage(1500);
const [loading, setLoading] = useState(false);
const [formState, { password, label }] = useFormState(null, {
withIds: true
});
const onSubmit = async e => {
e.preventDefault();
if (loading) return;
setLoading(true);
try {
await axios.post(
`${APIv2.Users}/delete`,
formState.values,
getAxiosConfig()
);
Router.push("/logout");
} catch (error) {
setMessage(error.response.data.error);
}
setLoading(false);
};
return (
<Col alignItems="flex-start" maxWidth="100%">
<H2 mb={4} bold>
Delete account
</H2>
<Text mb={4}>
Delete your account from {process.env.SITE_NAME}. All of your data
including your <Span bold>LINKS</Span> and <Span bold>STATS</Span> will
be deleted.
</Text>
<Text
{...label("password")}
as="label"
mb={[2, 3]}
fontSize={[15, 16]}
bold
>
Password
</Text>
<RowCenterV as="form" onSubmit={onSubmit}>
<TextInput placeholder="Password..." {...password("password")} mr={3} />
<Button color="red" type="submit" disabled={loading}>
<Icon name={loading ? "spinner" : "trash"} mr={2} stroke="white" />
{loading ? "Deleting..." : "Delete"}
</Button>
</RowCenterV>
<Text fontSize={15} mt={3} color={message.color}>
{message.text}
</Text>
</Col>
);
};
export default SettingsDeleteAccount;

View File

@ -1,6 +1,7 @@
import { NextPage } from "next";
import React from "react";
import SettingsDeleteAccount from "../components/Settings/SettingsDeleteAccount";
import SettingsPassword from "../components/Settings/SettingsPassword";
import SettingsDomain from "../components/Settings/SettingsDomain";
import SettingsApi from "../components/Settings/SettingsApi";
@ -30,6 +31,8 @@ const SettingsPage: NextPage = () => {
<SettingsPassword />
<Divider mt={4} mb={48} />
<SettingsApi />
<Divider mt={4} mb={48} />
<SettingsDeleteAccount />
</Col>
<Footer />
</AppWrapper>

View File

@ -12,3 +12,8 @@ export const get = async (req, res) => {
return res.status(200).send(data);
};
export const remove = async (req, res) => {
await query.user.remove(req.user);
return res.status(200).send("OK");
};

View File

@ -2,6 +2,7 @@ import { body, param } from "express-validator";
import { isAfter, subDays, subHours } from "date-fns";
import urlRegex from "url-regex";
import { promisify } from "util";
import bcrypt from "bcryptjs";
import axios from "axios";
import dns from "dns";
import URL from "url";
@ -94,7 +95,7 @@ export const createLink = [
});
req.body.domain = domain || null;
return !!domain;
if (!domain) return Promise.reject();
})
.withMessage("You can't use this domain.")
];
@ -125,12 +126,12 @@ export const addDomain = [
.withMessage("You can't use the default domain.")
.custom(async (value, { req }) => {
const domains = await query.domain.get({ user_id: req.user.id });
return domains.length === 0;
if (domains.length !== 0) return Promise.reject();
})
.withMessage("You already own a domain. Contact support if you need more.")
.custom(async value => {
const domain = await query.domain.find({ address: value });
return !domain || !domain.user_id || !domain.banned;
if (domain?.user_id || domain?.banned) return Promise.reject();
})
.withMessage("You can't add this domain."),
body("homepage")
@ -225,7 +226,7 @@ export const signup = [
req.user = user;
}
return !user || !user.verified;
if (user?.verified) return Promise.reject();
})
.withMessage("You can't use this email address.")
];
@ -259,6 +260,16 @@ export const resetPasswordRequest = [
.withMessage("Email length must be max 255.")
];
export const deleteUser = [
body("password", "Password is not valid.")
.exists({ checkFalsy: true, checkNull: true })
.isLength({ min: 8, max: 64 })
.custom(async (password, { req }) => {
const isMatch = await bcrypt.compare(password, req.user.password);
if (!isMatch) return Promise.reject();
})
];
export const cooldown = (user: User) => {
if (!env.GOOGLE_SAFE_BROWSING_KEY || !user || !user.cooldowns) return;

View File

@ -23,19 +23,9 @@ export async function createDomainTable(knex: Knex) {
.integer("user_id")
.references("id")
.inTable("users")
.onDelete("SET NULL")
.unique();
table.timestamps(false, true);
});
}
const hasUUID = await knex.schema.hasColumn("domains", "uuid");
if (!hasUUID) {
await knex.schema.raw('create extension if not exists "uuid-ossp"');
await knex.schema.alterTable("domains", table => {
table
.uuid("uuid")
.notNullable()
.defaultTo(knex.raw("uuid_generate_v4()"));
});
}
}

View File

@ -12,7 +12,6 @@ import Raven from "raven";
import * as helpers from "./handlers/helpers";
import * as links from "./handlers/links";
import * as auth from "./handlers/auth";
import { initializeDb } from "./knex";
import __v1Routes from "./__v1";
import routes from "./routes";
@ -28,8 +27,6 @@ const app = nextApp({ dir: "./client", dev: env.isDev });
const handle = app.getRequestHandler();
app.prepare().then(async () => {
await initializeDb();
const server = express();
server.set("trust proxy", true);