Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 0 additions & 16 deletions backend/.local.env.template

This file was deleted.

3 changes: 2 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"start": "nodemon src/index.ts",
"build": "tsc",
"serve": "node build/index.js",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit",
"generate": "graphql-codegen"
}
}
2 changes: 1 addition & 1 deletion backend/src/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import nodemailer from "nodemailer";

const router = express.Router();

const sendEmail = async (to: string, subject: string, text: string) => {
export const sendEmail = async (to: string, subject: string, text: string) => {
const transporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST!,
port: Number(process.env.EMAIL_PORT!),
Expand Down
36 changes: 34 additions & 2 deletions backend/src/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export type Message = {
__typename?: 'message';
content: Scalars['String']['output'];
created_at: Scalars['timestamp']['output'];
reply_to_uuid?: Maybe<Scalars['uuid']['output']>;
/** An object relationship */
room: Room;
room_uuid: Scalars['uuid']['output'];
Expand Down Expand Up @@ -141,6 +142,7 @@ export type Message_Bool_Exp = {
_or?: InputMaybe<Array<Message_Bool_Exp>>;
content?: InputMaybe<String_Comparison_Exp>;
created_at?: InputMaybe<Timestamp_Comparison_Exp>;
reply_to_uuid?: InputMaybe<Uuid_Comparison_Exp>;
room?: InputMaybe<Room_Bool_Exp>;
room_uuid?: InputMaybe<Uuid_Comparison_Exp>;
user?: InputMaybe<User_Bool_Exp>;
Expand All @@ -158,6 +160,7 @@ export enum Message_Constraint {
export type Message_Insert_Input = {
content?: InputMaybe<Scalars['String']['input']>;
created_at?: InputMaybe<Scalars['timestamp']['input']>;
reply_to_uuid?: InputMaybe<Scalars['uuid']['input']>;
room?: InputMaybe<Room_Obj_Rel_Insert_Input>;
room_uuid?: InputMaybe<Scalars['uuid']['input']>;
user?: InputMaybe<User_Obj_Rel_Insert_Input>;
Expand All @@ -170,6 +173,7 @@ export type Message_Max_Fields = {
__typename?: 'message_max_fields';
content?: Maybe<Scalars['String']['output']>;
created_at?: Maybe<Scalars['timestamp']['output']>;
reply_to_uuid?: Maybe<Scalars['uuid']['output']>;
room_uuid?: Maybe<Scalars['uuid']['output']>;
user_uuid?: Maybe<Scalars['uuid']['output']>;
uuid?: Maybe<Scalars['uuid']['output']>;
Expand All @@ -179,6 +183,7 @@ export type Message_Max_Fields = {
export type Message_Max_Order_By = {
content?: InputMaybe<Order_By>;
created_at?: InputMaybe<Order_By>;
reply_to_uuid?: InputMaybe<Order_By>;
room_uuid?: InputMaybe<Order_By>;
user_uuid?: InputMaybe<Order_By>;
uuid?: InputMaybe<Order_By>;
Expand All @@ -189,6 +194,7 @@ export type Message_Min_Fields = {
__typename?: 'message_min_fields';
content?: Maybe<Scalars['String']['output']>;
created_at?: Maybe<Scalars['timestamp']['output']>;
reply_to_uuid?: Maybe<Scalars['uuid']['output']>;
room_uuid?: Maybe<Scalars['uuid']['output']>;
user_uuid?: Maybe<Scalars['uuid']['output']>;
uuid?: Maybe<Scalars['uuid']['output']>;
Expand All @@ -198,6 +204,7 @@ export type Message_Min_Fields = {
export type Message_Min_Order_By = {
content?: InputMaybe<Order_By>;
created_at?: InputMaybe<Order_By>;
reply_to_uuid?: InputMaybe<Order_By>;
room_uuid?: InputMaybe<Order_By>;
user_uuid?: InputMaybe<Order_By>;
uuid?: InputMaybe<Order_By>;
Expand All @@ -223,6 +230,7 @@ export type Message_On_Conflict = {
export type Message_Order_By = {
content?: InputMaybe<Order_By>;
created_at?: InputMaybe<Order_By>;
reply_to_uuid?: InputMaybe<Order_By>;
room?: InputMaybe<Room_Order_By>;
room_uuid?: InputMaybe<Order_By>;
user?: InputMaybe<User_Order_By>;
Expand All @@ -242,6 +250,8 @@ export enum Message_Select_Column {
/** column name */
CreatedAt = 'created_at',
/** column name */
ReplyToUuid = 'reply_to_uuid',
/** column name */
RoomUuid = 'room_uuid',
/** column name */
UserUuid = 'user_uuid',
Expand All @@ -253,6 +263,7 @@ export enum Message_Select_Column {
export type Message_Set_Input = {
content?: InputMaybe<Scalars['String']['input']>;
created_at?: InputMaybe<Scalars['timestamp']['input']>;
reply_to_uuid?: InputMaybe<Scalars['uuid']['input']>;
room_uuid?: InputMaybe<Scalars['uuid']['input']>;
user_uuid?: InputMaybe<Scalars['uuid']['input']>;
uuid?: InputMaybe<Scalars['uuid']['input']>;
Expand All @@ -270,6 +281,7 @@ export type Message_Stream_Cursor_Input = {
export type Message_Stream_Cursor_Value_Input = {
content?: InputMaybe<Scalars['String']['input']>;
created_at?: InputMaybe<Scalars['timestamp']['input']>;
reply_to_uuid?: InputMaybe<Scalars['uuid']['input']>;
room_uuid?: InputMaybe<Scalars['uuid']['input']>;
user_uuid?: InputMaybe<Scalars['uuid']['input']>;
uuid?: InputMaybe<Scalars['uuid']['input']>;
Expand All @@ -282,6 +294,8 @@ export enum Message_Update_Column {
/** column name */
CreatedAt = 'created_at',
/** column name */
ReplyToUuid = 'reply_to_uuid',
/** column name */
RoomUuid = 'room_uuid',
/** column name */
UserUuid = 'user_uuid',
Expand Down Expand Up @@ -1319,7 +1333,7 @@ export type User_Room_Bool_Exp = {

/** unique or primary key constraints on table "user_room" */
export enum User_Room_Constraint {
/** unique or primary key constraint on columns "user_uuid", "room_uuid" */
/** unique or primary key constraint on columns "room_uuid", "user_uuid" */
UserRoomPkey = 'user_room_pkey'
}

Expand Down Expand Up @@ -1547,6 +1561,14 @@ export type AddUserMutationVariables = Exact<{

export type AddUserMutation = { __typename?: 'mutation_root', insert_user_one?: { __typename?: 'user', uuid: any } | null };

export type UpdateUserPasswordMutationVariables = Exact<{
uuid: Scalars['uuid']['input'];
password: Scalars['String']['input'];
}>;


export type UpdateUserPasswordMutation = { __typename?: 'mutation_root', update_user?: { __typename?: 'user_mutation_response', affected_rows: number } | null };

export type GetUsersByUsernameQueryVariables = Exact<{
username: Scalars['String']['input'];
}>;
Expand Down Expand Up @@ -1619,6 +1641,13 @@ export const AddUserDocument = gql`
}
}
`;
export const UpdateUserPasswordDocument = gql`
mutation updateUserPassword($uuid: uuid!, $password: String!) {
update_user(where: {uuid: {_eq: $uuid}}, _set: {password: $password}) {
affected_rows
}
}
`;
export const GetUsersByUsernameDocument = gql`
query getUsersByUsername($username: String!) {
user(where: {username: {_eq: $username}}) {
Expand Down Expand Up @@ -1656,9 +1685,12 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
addUser(variables: AddUserMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise<AddUserMutation> {
return withWrapper((wrappedRequestHeaders) => client.request<AddUserMutation>(AddUserDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'addUser', 'mutation', variables);
},
updateUserPassword(variables: UpdateUserPasswordMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise<UpdateUserPasswordMutation> {
return withWrapper((wrappedRequestHeaders) => client.request<UpdateUserPasswordMutation>(UpdateUserPasswordDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'updateUserPassword', 'mutation', variables);
},
getUsersByUsername(variables: GetUsersByUsernameQueryVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise<GetUsersByUsernameQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<GetUsersByUsernameQuery>(GetUsersByUsernameDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'getUsersByUsername', 'query', variables);
}
};
}
export type Sdk = ReturnType<typeof getSdk>;
export type Sdk = ReturnType<typeof getSdk>;
47 changes: 47 additions & 0 deletions backend/src/user.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import express from "express";
import jwt from "jsonwebtoken";
import { sdk as graphql } from "./index";
import { sendEmail } from "./email";

interface userJWTPayload {
uuid: string;
Expand Down Expand Up @@ -72,3 +73,49 @@ router.post("/register", async (req, res) => {
});

export default router;

router.post("/change-password/request", async (req, res) => {
const { username } = req.body;
if (!username) {
return res.status(422).send("Missing username");
}
try {
const queryResult = await graphql.getUsersByUsername({ username });
if (queryResult.user.length === 0) {
return res.status(404).send("User not found");
}
const user = queryResult.user[0];
const token = jwt.sign(
{ uuid: user.uuid },
process.env.JWT_SECRET!,
{ expiresIn: "1h" });
const link = `http://localhost:8888/user/change-password/action?token=${token}`;
await sendEmail(username, "Password Reset Request", `Click this link: ${link}`);
return res.status(200).send("Reset email sent");
} catch (err) {
console.error(err);
return res.sendStatus(500);
}
});

router.post("/change-password/action", async (req, res) => {
const { token, newPassword } = req.body;
if (!token || !newPassword) {
return res.status(422).send("Missing token or newPassword");
}
try {
const payload: any = jwt.verify(token, process.env.JWT_SECRET!);
const uuid = payload.uuid;
const result = await graphql.updateUserPassword({
uuid,
password: newPassword,
});
if (result.update_user?.affected_rows === 0) {
return res.status(404).send("User not found");
}
return res.status(200).send("Password updated successfully");
} catch (err) {
console.error(err);
return res.sendStatus(500);
}
});
2 changes: 0 additions & 2 deletions database/.local.env.template

This file was deleted.

6 changes: 6 additions & 0 deletions database/graphql/user.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ mutation addUser($username: String!, $password: String!) {
}
}

mutation updateUserPassword($uuid: uuid!, $password: String!) {
update_user(where: {uuid: {_eq: $uuid}}, _set: {password: $password}) {
affected_rows
}
}

query getUsersByUsername($username: String!) {
user(where: {username: {_eq: $username}}) {
uuid
Expand Down
Loading