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
1,982 changes: 1,982 additions & 0 deletions backend/package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"jsonwebtoken": "9.0.2",
"morgan": "1.10.0",
"multer": "1.4.5-lts.1",
"nodemailer": "6.9.14"
"nodemailer": "6.9.14",
"path-to-regexp": "^8.3.0"
},
"devDependencies": {
"@types/express": "4.17.21",
Expand Down
28 changes: 28 additions & 0 deletions backend/src/file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,32 @@ router.get("/download", authenticate, (req, res) => {
}
});

router.post("/delete", authenticate, (req, res) => {
const { room, filename }= req.body;

//databaseHW:3.2
if (!room || !filename) {
return res.status(422).send("422 Unprocessable Entity: Missing room or filename");
}

try {
const filePath = path.resolve(baseDir, room, filename);
if (!fs.existsSync(filePath)) {
return res.status(422).send("422 Unprocessable Entity: Missing file");
}
fs.unlinkSync(filePath);

return res.status(200).json({
message: "文件删除成功"
});
} catch (err) {
console.error("文件删除错误:", err);
return res.status(500).json({
error: "INTERNAL_SERVER_ERROR",
message: "服务器内部错误,请稍后重试",
code: "SERVER_ERROR"
});
}
})

export default router;
89 changes: 87 additions & 2 deletions backend/src/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1319,7 +1319,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 @@ -1508,6 +1508,20 @@ export type GetMessagesByRoomSubscriptionVariables = Exact<{

export type GetMessagesByRoomSubscription = { __typename?: 'subscription_root', message: Array<{ __typename?: 'message', uuid: any, content: string, created_at: any, user: { __typename?: 'user', uuid: any, username: string } }> };

export type GetMessageByUserQueryVariables = Exact<{
user_uuid?: InputMaybe<Scalars['uuid']['input']>;
}>;


export type GetMessageByUserQuery = { __typename?: 'query_root', message: Array<{ __typename?: 'message', user_uuid: any, room_uuid: any, content: string }> };

export type DeleteMessageByUsernameMutationVariables = Exact<{
user_uuid?: InputMaybe<Scalars['uuid']['input']>;
}>;


export type DeleteMessageByUsernameMutation = { __typename?: 'mutation_root', delete_message?: { __typename?: 'message_mutation_response', affected_rows: number, returning: Array<{ __typename?: 'message', content: string, user_uuid: any }> } | null };

export type AddRoomMutationVariables = Exact<{
name: Scalars['String']['input'];
intro: Scalars['String']['input'];
Expand Down Expand Up @@ -1539,6 +1553,14 @@ export type JoinRoomMutationVariables = Exact<{

export type JoinRoomMutation = { __typename?: 'mutation_root', insert_user_room_one?: { __typename?: 'user_room', user_uuid: any, room_uuid: any } | null };

export type QuitRoomMutationVariables = Exact<{
room_uuid?: InputMaybe<Scalars['uuid']['input']>;
user_uuid?: InputMaybe<Scalars['uuid']['input']>;
}>;


export type QuitRoomMutation = { __typename?: 'mutation_root', delete_user_room?: { __typename?: 'user_room_mutation_response', affected_rows: number, returning: Array<{ __typename?: 'user_room', room_uuid: any, user_uuid: any }> } | null };

export type AddUserMutationVariables = Exact<{
username: Scalars['String']['input'];
password: Scalars['String']['input'];
Expand All @@ -1554,6 +1576,13 @@ export type GetUsersByUsernameQueryVariables = Exact<{

export type GetUsersByUsernameQuery = { __typename?: 'query_root', user: Array<{ __typename?: 'user', uuid: any, password: string }> };

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


export type DeleteUserMutation = { __typename?: 'mutation_root', delete_user?: { __typename?: 'user_mutation_response', affected_rows: number, returning: Array<{ __typename?: 'user', username: string, uuid: any }> } | null };


export const AddMessageDocument = gql`
mutation addMessage($user_uuid: uuid!, $room_uuid: uuid!, $content: String!) {
Expand All @@ -1577,6 +1606,26 @@ export const GetMessagesByRoomDocument = gql`
}
}
`;
export const GetMessageByUserDocument = gql`
query getMessageByUser($user_uuid: uuid = "") {
message(where: {user_uuid: {_eq: $user_uuid}}) {
user_uuid
room_uuid
content
}
}
`;
export const DeleteMessageByUsernameDocument = gql`
mutation deleteMessageByUsername($user_uuid: uuid = "") {
delete_message(where: {user_uuid: {_eq: $user_uuid}}) {
affected_rows
returning {
content
user_uuid
}
}
}
`;
export const AddRoomDocument = gql`
mutation addRoom($name: String!, $intro: String!, $invite_code: String!) {
insert_room_one(object: {name: $name, intro: $intro, invite_code: $invite_code}) {
Expand Down Expand Up @@ -1612,6 +1661,19 @@ export const JoinRoomDocument = gql`
}
}
`;
export const QuitRoomDocument = gql`
mutation quitRoom($room_uuid: uuid = "", $user_uuid: uuid = "") {
delete_user_room(
where: {room_uuid: {_eq: $room_uuid}, user_uuid: {_eq: $user_uuid}}
) {
returning {
room_uuid
user_uuid
}
affected_rows
}
}
`;
export const AddUserDocument = gql`
mutation addUser($username: String!, $password: String!) {
insert_user_one(object: {username: $username, password: $password}) {
Expand All @@ -1627,6 +1689,17 @@ export const GetUsersByUsernameDocument = gql`
}
}
`;
export const DeleteUserDocument = gql`
mutation deleteUser($uuid: uuid!) {
delete_user(where: {uuid: {_eq: $uuid}}) {
affected_rows
returning {
username
uuid
}
}
}
`;

export type SdkFunctionWrapper = <T>(action: (requestHeaders?:Record<string, string>) => Promise<T>, operationName: string, operationType?: string, variables?: any) => Promise<T>;

Expand All @@ -1641,6 +1714,12 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
getMessagesByRoom(variables: GetMessagesByRoomSubscriptionVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise<GetMessagesByRoomSubscription> {
return withWrapper((wrappedRequestHeaders) => client.request<GetMessagesByRoomSubscription>(GetMessagesByRoomDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'getMessagesByRoom', 'subscription', variables);
},
getMessageByUser(variables?: GetMessageByUserQueryVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise<GetMessageByUserQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<GetMessageByUserQuery>(GetMessageByUserDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'getMessageByUser', 'query', variables);
},
deleteMessageByUsername(variables?: DeleteMessageByUsernameMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise<DeleteMessageByUsernameMutation> {
return withWrapper((wrappedRequestHeaders) => client.request<DeleteMessageByUsernameMutation>(DeleteMessageByUsernameDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'deleteMessageByUsername', 'mutation', variables);
},
addRoom(variables: AddRoomMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise<AddRoomMutation> {
return withWrapper((wrappedRequestHeaders) => client.request<AddRoomMutation>(AddRoomDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'addRoom', 'mutation', variables);
},
Expand All @@ -1653,12 +1732,18 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
joinRoom(variables: JoinRoomMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise<JoinRoomMutation> {
return withWrapper((wrappedRequestHeaders) => client.request<JoinRoomMutation>(JoinRoomDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'joinRoom', 'mutation', variables);
},
quitRoom(variables?: QuitRoomMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise<QuitRoomMutation> {
return withWrapper((wrappedRequestHeaders) => client.request<QuitRoomMutation>(QuitRoomDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'quitRoom', 'mutation', variables);
},
addUser(variables: AddUserMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise<AddUserMutation> {
return withWrapper((wrappedRequestHeaders) => client.request<AddUserMutation>(AddUserDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'addUser', 'mutation', variables);
},
getUsersByUsername(variables: GetUsersByUsernameQueryVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise<GetUsersByUsernameQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<GetUsersByUsernameQuery>(GetUsersByUsernameDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'getUsersByUsername', 'query', variables);
},
deleteUser(variables: DeleteUserMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise<DeleteUserMutation> {
return withWrapper((wrappedRequestHeaders) => client.request<DeleteUserMutation>(DeleteUserDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'deleteUser', 'mutation', variables);
}
};
}
export type Sdk = ReturnType<typeof getSdk>;
export type Sdk = ReturnType<typeof getSdk>;
29 changes: 28 additions & 1 deletion backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import emailRouter from "./email";

const app = express();
const address = "http://localhost";
const port = 8888;
const port =8888;

dotenv.config({
path: path.resolve(process.cwd(), ".local.env"),
Expand Down Expand Up @@ -42,6 +42,33 @@ app.use("/user", userRouter);
app.use("/file", fileRouter);
app.use("/email", emailRouter);

//databaseHW:2.3.2
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
console.error("未处理的错误:", err);

if (err.name === "JsonWebTokenError") {
return res.status(401).json({
error: "AUTHENTICATION_ERROR",
message: "无效的令牌",
code: "INVALID_TOKEN"
});
}

if (err.name === "TokenExpiredError") {
return res.status(401).json({
error: "AUTHENTICATION_ERROR",
message: "令牌已过期",
code: "TOKEN_EXPIRED"
});
}

res.status(500).json({
error: "INTERNAL_SERVER_ERROR",
message: "服务器内部错误",
code: "UNHANDLED_ERROR"
});
});

app.listen(port, () => {
console.log(`Server running at ${address}:${port}/`);
});
76 changes: 74 additions & 2 deletions backend/src/user.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
import express from "express";
import jwt from "jsonwebtoken";
import authenticate from "./authenticate";
import { sdk as graphql } from "./index";

// Pre-set some validation methods

const validatePassword = (password: string): boolean => {
return password.length >= 4 && /[a-zA-Z]/.test(password) && /\d/.test(password);
};

const validateUsername = (username: string): boolean => {
return username.length >= 3 && username.length <= 20 && /^[a-zA-Z0-9_]+$/.test(username);
};

const validateUUID = (uuid: string): boolean => {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
return uuidRegex.test(uuid);
};



interface userJWTPayload {
uuid: string;
"https://hasura.io/jwt/claims": {
Expand All @@ -15,24 +33,36 @@ const router = express.Router();
router.post("/login", async (req, res) => {
const { username, password } = req.body;
if (!username || !password) {
return res.status(422).send("422 Unprocessable Entity: Missing username or password");
return res.status(422).send("422 Unprocessable Entity: Missing username or password-login");
}
//databaseHW:2.3
if (!validatePassword(password)) {
return res.status(422).send( "密码必须至少4个字符,且包含字母和数字");
}


try {
console.log("test3");
const queryResult = await graphql.getUsersByUsername({ username: username });
console.log("test2");
if (queryResult.user.length === 0) {
return res.status(404).send("404 Not Found: User does not exist");
}
const user = queryResult.user[0];
if (user.password !== password) {
return res.status(401).send("401 Unauthorized: Password does not match");
}

console.log("test1");

const payload: userJWTPayload = {
uuid: user.uuid,
"https://hasura.io/jwt/claims": {
"x-hasura-allowed-roles": ["admin", "user"],
"x-hasura-default-role": "user",
},
};

const token = jwt.sign(payload, process.env.JWT_SECRET!, {
expiresIn: "24h",
});
Expand All @@ -46,8 +76,18 @@ router.post("/login", async (req, res) => {
router.post("/register", async (req, res) => {
const { username, password } = req.body;
if (!username || !password) {
return res.status(422).send("422 Unprocessable Entity: Missing username or password");
return res.status(422).send("422 Unprocessable Entity: Missing username or password-register");
}

//databaseHW:2.3
if (!validatePassword(password)) {
return res.status(422).send( "密码必须至少4个字符,且包含字母和数字");
}
if (!validateUsername(username)) {
return res.status(422).send("用户名必须至少3个字符,最多20个字符,且只能包含字母、数字、下划线");
}


try {
const queryResult = await graphql.getUsersByUsername({ username: username });
if (queryResult.user.length !== 0) {
Expand All @@ -71,4 +111,36 @@ router.post("/register", async (req, res) => {
}
});

router.get("/delete",authenticate,async(req,res)=>
{

try {

const authHeader = req.get("Authorization");
const token = authHeader!.substring(7);
console.log(token);
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as userJWTPayload;
console.log("decoded");
const userUuid = decoded.uuid;
//databaseHW:2.3
if (!validateUUID(userUuid)) {
return res.status(422).send("422 Unprocessable Entity: Invalid uuid");
}

//先删除信息,退出房间
const mutationResult1 = await graphql.deleteMessageByUsername({user_uuid: userUuid });
const mutationResult2 = await graphql.quitRoom({user_uuid: userUuid });

//最后再删除用户
const mutationResult = await graphql.deleteUser({ uuid: userUuid });
if (mutationResult.delete_user?.affected_rows === 0) {
return res.status(404).send("404 Not Found: User does not exist");
}
return res.status(200).json({ message: "User deleted successfully" });
} catch (err) {
console.error(err);
return res.sendStatus(500);
}
})

export default router;
Loading