upgrade request function
This commit is contained in:
parent
b4d6b14825
commit
3f798d5534
|
@ -203,7 +203,7 @@ server {
|
||||||
When a user upgrades their account level to `student` or `editor`:
|
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
|
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).
|
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).
|
||||||
|
|
||||||
|
|
|
@ -95,8 +95,10 @@ export async function updateLingdocsUser(uuid: T.UUID, toUpdate:
|
||||||
level: "student",
|
level: "student",
|
||||||
wordlistDbName: T.WordlistDbName,
|
wordlistDbName: T.WordlistDbName,
|
||||||
couchDbPassword: T.UserDbPassword,
|
couchDbPassword: T.UserDbPassword,
|
||||||
|
requestedUpgradeToStudent: undefined,
|
||||||
} |
|
} |
|
||||||
{ userTextOptionsRecord: T.UserTextOptionsRecord }
|
{ userTextOptionsRecord: T.UserTextOptionsRecord } |
|
||||||
|
{ requestedUpgradeToStudent: true }
|
||||||
): Promise<T.LingdocsUser> {
|
): Promise<T.LingdocsUser> {
|
||||||
const user = await getLingdocsUser("userId", uuid);
|
const user = await getLingdocsUser("userId", uuid);
|
||||||
if (!user) throw new Error("unable to update - user not found " + uuid);
|
if (!user) throw new Error("unable to update - user not found " + uuid);
|
||||||
|
|
|
@ -5,7 +5,7 @@ import * as T from "../../../website/src/lib/account-types";
|
||||||
|
|
||||||
type Address = string | { name: string, address: string };
|
type Address = string | { name: string, address: string };
|
||||||
|
|
||||||
const from: Address = {
|
const adminAddress: Address = {
|
||||||
name: "LingDocs Admin",
|
name: "LingDocs Admin",
|
||||||
address: "admin@lingdocs.com",
|
address: "admin@lingdocs.com",
|
||||||
};
|
};
|
||||||
|
@ -31,31 +31,53 @@ const transporter = nodemailer.createTransport({
|
||||||
|
|
||||||
async function sendEmail(to: Address, subject: string, text: string) {
|
async function sendEmail(to: Address, subject: string, text: string) {
|
||||||
await transporter.sendMail({
|
await transporter.sendMail({
|
||||||
from,
|
from: adminAddress,
|
||||||
to,
|
to,
|
||||||
subject,
|
subject,
|
||||||
text,
|
text,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: MAKE THIS
|
// TODO: MAKE THIS A URL ACROSS PROJECT
|
||||||
const baseURL = inProd ? "https://account.lingdocs.com" : "http://localhost:4000";
|
const baseURL = inProd ? "https://account.lingdocs.com" : "http://localhost:4000";
|
||||||
|
|
||||||
export async function sendVerificationEmail(user: T.LingdocsUser, token: T.URLToken) {
|
export async function sendVerificationEmail(user: T.LingdocsUser, token: T.URLToken) {
|
||||||
|
const subject = "Please Verify Your E-mail";
|
||||||
const content = `Hello ${user.name},
|
const content = `Hello ${user.name},
|
||||||
|
|
||||||
Please verify your email by visiting this link: ${baseURL}/email-verification/${user.userId}/${token}
|
Please verify your email by visiting this link: ${baseURL}/email-verification/${user.userId}/${token}
|
||||||
|
|
||||||
LingDocs Admin`;
|
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) {
|
export async function sendPasswordResetEmail(user: T.LingdocsUser, token: T.URLToken) {
|
||||||
|
const subject = "Reset Your Password";
|
||||||
const content = `Hello ${user.name},
|
const content = `Hello ${user.name},
|
||||||
|
|
||||||
Please visit this link to reset your password: ${baseURL}/password-reset/${user.userId}/${token}
|
Please visit this link to reset your password: ${baseURL}/password-reset/${user.userId}/${token}
|
||||||
|
|
||||||
LingDocs Admin`;
|
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);
|
||||||
}
|
}
|
|
@ -12,6 +12,8 @@ import {
|
||||||
getEmailTokenAndHash,
|
getEmailTokenAndHash,
|
||||||
} from "../lib/password-utils";
|
} from "../lib/password-utils";
|
||||||
import {
|
import {
|
||||||
|
sendAccountUpgradeMessage,
|
||||||
|
sendUpgradeRequestToAdmin,
|
||||||
sendVerificationEmail,
|
sendVerificationEmail,
|
||||||
} from "../lib/mail-utils";
|
} from "../lib/mail-utils";
|
||||||
import * as T from "../../../website/src/lib/account-types";
|
import * as T from "../../../website/src/lib/account-types";
|
||||||
|
@ -103,7 +105,7 @@ apiRouter.put("/user/userTextOptionsRecord", async (req, res, next) => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
next(e);
|
next(e);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
apiRouter.put("/user/upgrade", async (req, res, next) => {
|
apiRouter.put("/user/upgrade", async (req, res, next) => {
|
||||||
if (!req.user) throw new Error("user not found");
|
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
|
// add user to couchdb authentication db
|
||||||
const { password, userDbName } = await addCouchDbAuthUser(userId);
|
const { password, userDbName } = await addCouchDbAuthUser(userId);
|
||||||
// // create user db
|
// // create user db
|
||||||
// const { name } = await createWordlistDatabase(userId, password);
|
|
||||||
// update LingdocsUser
|
// 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 = {
|
const upgraded: T.UpgradeUserResponse = {
|
||||||
ok: true,
|
ok: true,
|
||||||
message: "user upgraded to student",
|
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
|
* deletes a users own account
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -38,6 +38,7 @@ export type LingdocsUser = {
|
||||||
tokenHash: Hash,
|
tokenHash: Hash,
|
||||||
requestedOn: TimeStamp,
|
requestedOn: TimeStamp,
|
||||||
},
|
},
|
||||||
|
requestedUpgradeToStudent?: boolean,
|
||||||
tests: [],
|
tests: [],
|
||||||
lastLogin: TimeStamp,
|
lastLogin: TimeStamp,
|
||||||
lastActive: TimeStamp,
|
lastActive: TimeStamp,
|
||||||
|
|
|
@ -23,6 +23,10 @@ export async function upgradeAccount(password: string): Promise<AT.UpgradeUserRe
|
||||||
return response as AT.UpgradeUserResponse;
|
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> {
|
export async function updateUserTextOptionsRecord(userTextOptionsRecord: AT.UserTextOptionsRecord): Promise<AT.UpdateUserTextOptionsRecordResponse> {
|
||||||
const response = await myFetch("account", "user/userTextOptionsRecord", "PUT", { userTextOptionsRecord }) as AT.UpdateUserTextOptionsRecordResponse;
|
const response = await myFetch("account", "user/userTextOptionsRecord", "PUT", { userTextOptionsRecord }) as AT.UpdateUserTextOptionsRecordResponse;
|
||||||
return response;
|
return response;
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
upgradeAccount,
|
upgradeAccount,
|
||||||
signOut,
|
signOut,
|
||||||
publishDictionary,
|
publishDictionary,
|
||||||
|
upgradeToStudentRequest,
|
||||||
} from "../lib/backend-calls";
|
} from "../lib/backend-calls";
|
||||||
import LoadingElipses from "../components/LoadingElipses";
|
import LoadingElipses from "../components/LoadingElipses";
|
||||||
import { Helmet } from "react-helmet";
|
import { Helmet } from "react-helmet";
|
||||||
|
@ -51,6 +52,22 @@ const Account = ({ user, loadUser }: { user: AT.LingdocsUser | undefined, loadUs
|
||||||
setUpgradePassword("");
|
setUpgradePassword("");
|
||||||
setUpgradeError("");
|
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() {
|
async function handleUpgrade() {
|
||||||
setUpgradeError("");
|
setUpgradeError("");
|
||||||
setWaiting(true);
|
setWaiting(true);
|
||||||
|
@ -136,7 +153,7 @@ const Account = ({ user, loadUser }: { user: AT.LingdocsUser | undefined, loadUs
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>}
|
</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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -167,7 +184,7 @@ const Account = ({ user, loadUser }: { user: AT.LingdocsUser | undefined, loadUs
|
||||||
<Modal.Header closeButton>
|
<Modal.Header closeButton>
|
||||||
<Modal.Title>Upgrade Account</Modal.Title>
|
<Modal.Title>Upgrade Account</Modal.Title>
|
||||||
</Modal.Header>
|
</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">
|
<div className="form-group px-3">
|
||||||
<label htmlFor="upgradePasswordForm">Upgrade password:</label>
|
<label htmlFor="upgradePasswordForm">Upgrade password:</label>
|
||||||
<input
|
<input
|
||||||
|
|
Loading…
Reference in New Issue