upgrade request function

This commit is contained in:
lingdocs 2021-08-25 12:06:37 +04:00
parent b4d6b14825
commit 3f798d5534
7 changed files with 79 additions and 12 deletions

View File

@ -203,7 +203,7 @@ server {
When a user upgrades their account level to `student` or `editor`:
1. A doc in the `_users` db is created with their Firebase Authentication info, account level, and a password they can use for syncing their personal wordlistdb
2. A user database is created (by the firebase functions - *not* by the couchdb_peruser) which they use to sync their personal wordlist.
2. A user database is created (automatically by `couchdb_peruser`) which they use to sync their personal wordlist.
There is also a `review-tasks` database which is used to store all the review tasks for editors and syncs with the review tasks in the app for the editor(s).

View File

@ -95,8 +95,10 @@ export async function updateLingdocsUser(uuid: T.UUID, toUpdate:
level: "student",
wordlistDbName: T.WordlistDbName,
couchDbPassword: T.UserDbPassword,
requestedUpgradeToStudent: undefined,
} |
{ userTextOptionsRecord: T.UserTextOptionsRecord }
{ userTextOptionsRecord: T.UserTextOptionsRecord } |
{ requestedUpgradeToStudent: true }
): Promise<T.LingdocsUser> {
const user = await getLingdocsUser("userId", uuid);
if (!user) throw new Error("unable to update - user not found " + uuid);

View File

@ -5,7 +5,7 @@ import * as T from "../../../website/src/lib/account-types";
type Address = string | { name: string, address: string };
const from: Address = {
const adminAddress: Address = {
name: "LingDocs Admin",
address: "admin@lingdocs.com",
};
@ -31,31 +31,53 @@ const transporter = nodemailer.createTransport({
async function sendEmail(to: Address, subject: string, text: string) {
await transporter.sendMail({
from,
from: adminAddress,
to,
subject,
text,
});
}
// TODO: MAKE THIS
// TODO: MAKE THIS A URL ACROSS PROJECT
const baseURL = inProd ? "https://account.lingdocs.com" : "http://localhost:4000";
export async function sendVerificationEmail(user: T.LingdocsUser, token: T.URLToken) {
const subject = "Please Verify Your E-mail";
const content = `Hello ${user.name},
Please verify your email by visiting this link: ${baseURL}/email-verification/${user.userId}/${token}
LingDocs Admin`;
await sendEmail(getAddress(user), "Please Verify Your E-mail", content);
await sendEmail(getAddress(user), subject, content);
}
export async function sendPasswordResetEmail(user: T.LingdocsUser, token: T.URLToken) {
const subject = "Reset Your Password";
const content = `Hello ${user.name},
Please visit this link to reset your password: ${baseURL}/password-reset/${user.userId}/${token}
LingDocs Admin`;
await sendEmail(getAddress(user), "Reset Your Password", content);
await sendEmail(getAddress(user), subject, content);
}
export async function sendAccountUpgradeMessage(user: T.LingdocsUser) {
const subject = "You've Upgraded to Student";
const content = `Hello ${user.name},
Congratulations on your upgrade to a LingDocs Student account! 👨🎓
Now you can start using your wordlist in the dictionary. It will automatically sync across any devices you're signed in to.
LingDocs Admin`;
await sendEmail(getAddress(user), subject, content);
}
export async function sendUpgradeRequestToAdmin(user: T.LingdocsUser) {
const subject = "Account Upgrade Request";
const content = `${user.name} - ${user.email} - ${user.userId} is requesting to upgrade to student.`;
await sendEmail(adminAddress, subject, content);
}

View File

@ -12,6 +12,8 @@ import {
getEmailTokenAndHash,
} from "../lib/password-utils";
import {
sendAccountUpgradeMessage,
sendUpgradeRequestToAdmin,
sendVerificationEmail,
} from "../lib/mail-utils";
import * as T from "../../../website/src/lib/account-types";
@ -103,7 +105,7 @@ apiRouter.put("/user/userTextOptionsRecord", async (req, res, next) => {
} catch (e) {
next(e);
}
})
});
apiRouter.put("/user/upgrade", async (req, res, next) => {
if (!req.user) throw new Error("user not found");
@ -133,9 +135,14 @@ apiRouter.put("/user/upgrade", async (req, res, next) => {
// add user to couchdb authentication db
const { password, userDbName } = await addCouchDbAuthUser(userId);
// // create user db
// const { name } = await createWordlistDatabase(userId, password);
// update LingdocsUser
const u = await updateLingdocsUser(userId, { level: "student", wordlistDbName: userDbName, couchDbPassword: password });
const u = await updateLingdocsUser(userId, {
level: "student",
wordlistDbName: userDbName,
couchDbPassword: password,
requestedUpgradeToStudent: undefined,
});
sendAccountUpgradeMessage(u).catch(console.error);
const upgraded: T.UpgradeUserResponse = {
ok: true,
message: "user upgraded to student",
@ -147,6 +154,20 @@ apiRouter.put("/user/upgrade", async (req, res, next) => {
}
});
apiRouter.post("/user/upgradeToStudentRequest", async (req, res, next) => {
if (!req.user) throw new Error("user not found");
try {
if (req.user.level === "student" || req.user.level === "editor") {
res.send({ ok: true, message: "user already upgraded "});
return;
}
sendUpgradeRequestToAdmin(req.user).catch(console.error);
await updateLingdocsUser(req.user.userId, { requestedUpgradeToStudent: true });
} catch (e) {
next(e);
}
});
/**
* deletes a users own account
*/

View File

@ -38,6 +38,7 @@ export type LingdocsUser = {
tokenHash: Hash,
requestedOn: TimeStamp,
},
requestedUpgradeToStudent?: boolean,
tests: [],
lastLogin: TimeStamp,
lastActive: TimeStamp,

View File

@ -23,6 +23,10 @@ export async function upgradeAccount(password: string): Promise<AT.UpgradeUserRe
return response as AT.UpgradeUserResponse;
}
export async function upgradeToStudentRequest(): Promise<AT.APIResponse> {
return await myFetch("account", "upgradeToStudentRequest", "POST") as AT.APIResponse;
}
export async function updateUserTextOptionsRecord(userTextOptionsRecord: AT.UserTextOptionsRecord): Promise<AT.UpdateUserTextOptionsRecordResponse> {
const response = await myFetch("account", "user/userTextOptionsRecord", "PUT", { userTextOptionsRecord }) as AT.UpdateUserTextOptionsRecordResponse;
return response;

View File

@ -7,6 +7,7 @@ import {
upgradeAccount,
signOut,
publishDictionary,
upgradeToStudentRequest,
} from "../lib/backend-calls";
import LoadingElipses from "../components/LoadingElipses";
import { Helmet } from "react-helmet";
@ -51,6 +52,22 @@ const Account = ({ user, loadUser }: { user: AT.LingdocsUser | undefined, loadUs
setUpgradePassword("");
setUpgradeError("");
}
async function handleUpgradeRequest() {
setUpgradeError("");
setWaiting(true);
upgradeToStudentRequest().then((res) => {
setWaiting(false);
if (res.ok) {
loadUser();
closeUpgrade();
} else {
setUpgradeError("Error requesting upgrade");
}
}).catch((err) => {
setWaiting(false);
setUpgradeError(err.message);
});
}
async function handleUpgrade() {
setUpgradeError("");
setWaiting(true);
@ -136,7 +153,7 @@ const Account = ({ user, loadUser }: { user: AT.LingdocsUser | undefined, loadUs
</div>
</div>
</li>}
<li className="list-group-item">Account Level: {capitalize(user.level)}</li>
<li className="list-group-item">Account Level: {capitalize(user.level)} {user.requestedUpgradeToStudent ? "(Upgrade Requested)" : ""}</li>
</ul>
</div>
</div>
@ -167,7 +184,7 @@ const Account = ({ user, loadUser }: { user: AT.LingdocsUser | undefined, loadUs
<Modal.Header closeButton>
<Modal.Title>Upgrade Account</Modal.Title>
</Modal.Header>
<Modal.Body>Enter the secret upgrade password to upgrade your account.</Modal.Body>
<Modal.Body>Enter the secret upgrade password to upgrade your account or <a href="javascript:;" onClick={handleUpgradeRequest}>request an upgrade</a>.</Modal.Body>
<div className="form-group px-3">
<label htmlFor="upgradePasswordForm">Upgrade password:</label>
<input