Compare commits
No commits in common. "e6f73932faccd4fca040792dc946d7314a230efc" and "be57bd32b8ac8e8fbbd7f3270f4c702340bd1b5f" have entirely different histories.
e6f73932fa
...
be57bd32b8
|
|
@ -100,26 +100,18 @@
|
||||||
"User": {
|
"User": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "User ID"
|
"description": "User ID"
|
||||||
},
|
},
|
||||||
"firstName": {
|
"firstName": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "User first name"
|
"description": "User first name"
|
||||||
},
|
},
|
||||||
"isPrivate": {
|
"lastName": {
|
||||||
"type": "boolean",
|
"type": "string",
|
||||||
"description": "User private status"
|
"description": "User last name"
|
||||||
},
|
}
|
||||||
"profilePictureId": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "User profile picture ID"
|
|
||||||
},
|
|
||||||
"lastName": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "User last name"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"UserWithRelations": {
|
"UserWithRelations": {
|
||||||
|
|
@ -776,71 +768,29 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/users/me": {
|
"/users/me": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["Users"],
|
"tags": [
|
||||||
"summary": "Get the currently logged in user",
|
"Users"
|
||||||
"security": [
|
],
|
||||||
{
|
"summary": "Get the currently logged in user",
|
||||||
"bearerAuth": []
|
"security": [
|
||||||
}
|
{
|
||||||
],
|
"bearerAuth": []
|
||||||
"responses": {
|
}
|
||||||
"200": {
|
],
|
||||||
"description": "The currently logged in user",
|
"responses": {
|
||||||
"content": {
|
"200": {
|
||||||
"application/json": {
|
"description": "The currently logged in user",
|
||||||
"schema": {
|
"content": {
|
||||||
"$ref": "#/components/schemas/UserWithRelationsAndNotifications"
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/UserWithRelationsAndNotifications"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"patch": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"bearerAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": ["Users"],
|
|
||||||
"summary": "Update the currently logged in user",
|
|
||||||
"requestBody": {
|
|
||||||
"required": true,
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"isPrivate": {
|
|
||||||
"type": "boolean",
|
|
||||||
"description": "Whether the user's account is private"
|
|
||||||
},
|
|
||||||
"profilePictureId": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "The ID of the user's profile picture"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "Successfully updated the user",
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/UserWithRelationsAndNotifications"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"400": {
|
|
||||||
"description": "Invalid request body"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"/users/{id}/follow": {
|
"/users/{id}/follow": {
|
||||||
"post": {
|
"post": {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
.gitignore
|
.gitignore
|
||||||
.npmignore
|
.npmignore
|
||||||
|
.openapi-generator-ignore
|
||||||
api.ts
|
api.ts
|
||||||
base.ts
|
base.ts
|
||||||
common.ts
|
common.ts
|
||||||
|
|
|
||||||
|
|
@ -284,18 +284,6 @@ export interface User {
|
||||||
* @memberof User
|
* @memberof User
|
||||||
*/
|
*/
|
||||||
'firstName'?: string;
|
'firstName'?: string;
|
||||||
/**
|
|
||||||
* User private status
|
|
||||||
* @type {boolean}
|
|
||||||
* @memberof User
|
|
||||||
*/
|
|
||||||
'isPrivate'?: boolean;
|
|
||||||
/**
|
|
||||||
* User profile picture ID
|
|
||||||
* @type {string}
|
|
||||||
* @memberof User
|
|
||||||
*/
|
|
||||||
'profilePictureId'?: string;
|
|
||||||
/**
|
/**
|
||||||
* User last name
|
* User last name
|
||||||
* @type {string}
|
* @type {string}
|
||||||
|
|
@ -321,18 +309,6 @@ export interface UserWithRelations {
|
||||||
* @memberof UserWithRelations
|
* @memberof UserWithRelations
|
||||||
*/
|
*/
|
||||||
'firstName'?: string;
|
'firstName'?: string;
|
||||||
/**
|
|
||||||
* User private status
|
|
||||||
* @type {boolean}
|
|
||||||
* @memberof UserWithRelations
|
|
||||||
*/
|
|
||||||
'isPrivate'?: boolean;
|
|
||||||
/**
|
|
||||||
* User profile picture ID
|
|
||||||
* @type {string}
|
|
||||||
* @memberof UserWithRelations
|
|
||||||
*/
|
|
||||||
'profilePictureId'?: string;
|
|
||||||
/**
|
/**
|
||||||
* User last name
|
* User last name
|
||||||
* @type {string}
|
* @type {string}
|
||||||
|
|
@ -382,18 +358,6 @@ export interface UserWithRelationsAndNotifications {
|
||||||
* @memberof UserWithRelationsAndNotifications
|
* @memberof UserWithRelationsAndNotifications
|
||||||
*/
|
*/
|
||||||
'firstName'?: string;
|
'firstName'?: string;
|
||||||
/**
|
|
||||||
* User private status
|
|
||||||
* @type {boolean}
|
|
||||||
* @memberof UserWithRelationsAndNotifications
|
|
||||||
*/
|
|
||||||
'isPrivate'?: boolean;
|
|
||||||
/**
|
|
||||||
* User profile picture ID
|
|
||||||
* @type {string}
|
|
||||||
* @memberof UserWithRelationsAndNotifications
|
|
||||||
*/
|
|
||||||
'profilePictureId'?: string;
|
|
||||||
/**
|
/**
|
||||||
* User last name
|
* User last name
|
||||||
* @type {string}
|
* @type {string}
|
||||||
|
|
@ -431,25 +395,6 @@ export interface UserWithRelationsAndNotifications {
|
||||||
*/
|
*/
|
||||||
'notifications'?: Array<Notification>;
|
'notifications'?: Array<Notification>;
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @export
|
|
||||||
* @interface UsersMePatchRequest
|
|
||||||
*/
|
|
||||||
export interface UsersMePatchRequest {
|
|
||||||
/**
|
|
||||||
* Whether the user\'s account is private
|
|
||||||
* @type {boolean}
|
|
||||||
* @memberof UsersMePatchRequest
|
|
||||||
*/
|
|
||||||
'isPrivate'?: boolean;
|
|
||||||
/**
|
|
||||||
* The ID of the user\'s profile picture
|
|
||||||
* @type {string}
|
|
||||||
* @memberof UsersMePatchRequest
|
|
||||||
*/
|
|
||||||
'profilePictureId'?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AuthenticationApi - axios parameter creator
|
* AuthenticationApi - axios parameter creator
|
||||||
|
|
@ -1583,46 +1528,6 @@ export const UsersApiAxiosParamCreator = function (configuration?: Configuration
|
||||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
|
||||||
return {
|
|
||||||
url: toPathString(localVarUrlObj),
|
|
||||||
options: localVarRequestOptions,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @summary Update the currently logged in user
|
|
||||||
* @param {UsersMePatchRequest} usersMePatchRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
usersMePatch: async (usersMePatchRequest: UsersMePatchRequest, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
|
||||||
// verify required parameter 'usersMePatchRequest' is not null or undefined
|
|
||||||
assertParamExists('usersMePatch', 'usersMePatchRequest', usersMePatchRequest)
|
|
||||||
const localVarPath = `/users/me`;
|
|
||||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
|
||||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
|
||||||
let baseOptions;
|
|
||||||
if (configuration) {
|
|
||||||
baseOptions = configuration.baseOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
const localVarRequestOptions = { method: 'PATCH', ...baseOptions, ...options};
|
|
||||||
const localVarHeaderParameter = {} as any;
|
|
||||||
const localVarQueryParameter = {} as any;
|
|
||||||
|
|
||||||
// authentication bearerAuth required
|
|
||||||
// http bearer authentication required
|
|
||||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
|
||||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
|
||||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
|
||||||
localVarRequestOptions.data = serializeDataIfNeeded(usersMePatchRequest, localVarRequestOptions, configuration)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url: toPathString(localVarUrlObj),
|
url: toPathString(localVarUrlObj),
|
||||||
options: localVarRequestOptions,
|
options: localVarRequestOptions,
|
||||||
|
|
@ -1701,19 +1606,6 @@ export const UsersApiFp = function(configuration?: Configuration) {
|
||||||
const localVarOperationServerBasePath = operationServerMap['UsersApi.usersMeGet']?.[localVarOperationServerIndex]?.url;
|
const localVarOperationServerBasePath = operationServerMap['UsersApi.usersMeGet']?.[localVarOperationServerIndex]?.url;
|
||||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @summary Update the currently logged in user
|
|
||||||
* @param {UsersMePatchRequest} usersMePatchRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
async usersMePatch(usersMePatchRequest: UsersMePatchRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserWithRelationsAndNotifications>> {
|
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.usersMePatch(usersMePatchRequest, options);
|
|
||||||
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
|
||||||
const localVarOperationServerBasePath = operationServerMap['UsersApi.usersMePatch']?.[localVarOperationServerIndex]?.url;
|
|
||||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1772,16 +1664,6 @@ export const UsersApiFactory = function (configuration?: Configuration, basePath
|
||||||
usersMeGet(options?: any): AxiosPromise<UserWithRelationsAndNotifications> {
|
usersMeGet(options?: any): AxiosPromise<UserWithRelationsAndNotifications> {
|
||||||
return localVarFp.usersMeGet(options).then((request) => request(axios, basePath));
|
return localVarFp.usersMeGet(options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @summary Update the currently logged in user
|
|
||||||
* @param {UsersMePatchRequest} usersMePatchRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
usersMePatch(usersMePatchRequest: UsersMePatchRequest, options?: any): AxiosPromise<UserWithRelationsAndNotifications> {
|
|
||||||
return localVarFp.usersMePatch(usersMePatchRequest, options).then((request) => request(axios, basePath));
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1849,18 +1731,6 @@ export class UsersApi extends BaseAPI {
|
||||||
public usersMeGet(options?: RawAxiosRequestConfig) {
|
public usersMeGet(options?: RawAxiosRequestConfig) {
|
||||||
return UsersApiFp(this.configuration).usersMeGet(options).then((request) => request(this.axios, this.basePath));
|
return UsersApiFp(this.configuration).usersMeGet(options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @summary Update the currently logged in user
|
|
||||||
* @param {UsersMePatchRequest} usersMePatchRequest
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
* @memberof UsersApi
|
|
||||||
*/
|
|
||||||
public usersMePatch(usersMePatchRequest: UsersMePatchRequest, options?: RawAxiosRequestConfig) {
|
|
||||||
return UsersApiFp(this.configuration).usersMePatch(usersMePatchRequest, options).then((request) => request(this.axios, this.basePath));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,11 @@ import { Action, ThunkAction, configureStore } from "@reduxjs/toolkit";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import loginReducer from "./loginSlice";
|
import loginReducer from "./loginSlice";
|
||||||
import postReducer from "./postSlice";
|
import postReducer from "./postSlice";
|
||||||
import usersReducer from "./usersSlice";
|
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
login: loginReducer,
|
login: loginReducer,
|
||||||
post: postReducer,
|
post: postReducer,
|
||||||
users: usersReducer,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
import { Store, createSlice } from "@reduxjs/toolkit";
|
|
||||||
import { Configuration, User, UserWithRelations, UsersApi } from "../api";
|
|
||||||
import { Status } from "../util/types";
|
|
||||||
import { AppThunk, store } from "./store";
|
|
||||||
|
|
||||||
interface userState {
|
|
||||||
status: Status;
|
|
||||||
error: string | null;
|
|
||||||
users: User[];
|
|
||||||
userWithRelations: UserWithRelations | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialState: userState = {
|
|
||||||
status: Status.idle,
|
|
||||||
error: null,
|
|
||||||
users: [],
|
|
||||||
userWithRelations: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const usersSlice = createSlice({
|
|
||||||
name: "users",
|
|
||||||
initialState,
|
|
||||||
reducers: {
|
|
||||||
setUsers: (state, action) => {
|
|
||||||
state.users = action.payload;
|
|
||||||
},
|
|
||||||
setStatus: (state, action) => {
|
|
||||||
state.status = action.payload;
|
|
||||||
},
|
|
||||||
setError: (state, action) => {
|
|
||||||
state.error = action.payload;
|
|
||||||
},
|
|
||||||
setUserWithRelations: (state, action) => {
|
|
||||||
state.userWithRelations = action.payload;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const getUsers = (): AppThunk => async (dispatch) => {
|
|
||||||
const api = createApi(store);
|
|
||||||
|
|
||||||
dispatch(setStatus(Status.loading));
|
|
||||||
try {
|
|
||||||
const response = await api.usersGet();
|
|
||||||
dispatch(setUsers(response.data));
|
|
||||||
dispatch(setStatus(Status.idle));
|
|
||||||
} catch (error) {
|
|
||||||
dispatch(setError((error as Error).message));
|
|
||||||
dispatch(setStatus(Status.idle));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getUserWithRelations =
|
|
||||||
(userId: number): AppThunk =>
|
|
||||||
async (dispatch) => {
|
|
||||||
const api = createApi(store);
|
|
||||||
|
|
||||||
dispatch(setStatus(Status.loading));
|
|
||||||
try {
|
|
||||||
const response = await api.usersIdGet(userId);
|
|
||||||
dispatch(setUserWithRelations(response));
|
|
||||||
dispatch(setStatus(Status.idle));
|
|
||||||
} catch (error) {
|
|
||||||
dispatch(setError((error as Error).message));
|
|
||||||
dispatch(setStatus(Status.idle));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const followUser =
|
|
||||||
(userId: number): AppThunk =>
|
|
||||||
async (dispatch) => {
|
|
||||||
const api = createApi(store);
|
|
||||||
|
|
||||||
dispatch(setStatus(Status.loading));
|
|
||||||
try {
|
|
||||||
await api.usersIdFollowPost(userId);
|
|
||||||
dispatch(getUsers());
|
|
||||||
} catch (error) {
|
|
||||||
dispatch(setError((error as Error).message));
|
|
||||||
dispatch(setStatus(Status.idle));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const unFollowUser =
|
|
||||||
(userId: number): AppThunk =>
|
|
||||||
async (dispatch) => {
|
|
||||||
const api = createApi(store);
|
|
||||||
|
|
||||||
dispatch(setStatus(Status.loading));
|
|
||||||
try {
|
|
||||||
await api.usersIdFollowDelete(userId);
|
|
||||||
dispatch(getUsers());
|
|
||||||
} catch (error) {
|
|
||||||
dispatch(setError((error as Error).message));
|
|
||||||
dispatch(setStatus(Status.idle));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const { setUsers, setStatus, setError, setUserWithRelations } =
|
|
||||||
usersSlice.actions;
|
|
||||||
|
|
||||||
export default usersSlice.reducer;
|
|
||||||
|
|
||||||
export const selectUsers = (state: { users: userState }) => state.users.users;
|
|
||||||
export const selectAUser = (state: { users: userState }) =>
|
|
||||||
state.users.userWithRelations;
|
|
||||||
export const selectStatus = (state: { users: userState }) => state.users.status;
|
|
||||||
export const selectError = (state: { users: userState }) => state.users.error;
|
|
||||||
|
|
||||||
function createApi(store: Store) {
|
|
||||||
const configuration = new Configuration({
|
|
||||||
basePath: process.env.REACT_APP_BACKEND_URL,
|
|
||||||
accessToken: store.getState().login.userInfo.jwt,
|
|
||||||
});
|
|
||||||
return new UsersApi(configuration);
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import { Avatar } from "@mui/material";
|
|
||||||
import { User, UserWithRelations } from "../api";
|
|
||||||
|
|
||||||
interface AppAvatarProps {
|
|
||||||
user: User | UserWithRelations;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AppAvatar(props: AppAvatarProps) {
|
|
||||||
return (
|
|
||||||
<Avatar
|
|
||||||
alt={`${props.user.firstName} ${props.user.lastName}`}
|
|
||||||
src={`/images/${props.user.profilePictureId}`}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -5,13 +5,13 @@ import Toolbar from "@mui/material/Toolbar";
|
||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import Menu from "@mui/material/Menu";
|
import Menu from "@mui/material/Menu";
|
||||||
|
import Avatar from "@mui/material/Avatar";
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
import MenuItem from "@mui/material/MenuItem";
|
||||||
import NotificationBell from "./notificationBell";
|
import NotificationBell from "./notificationBell";
|
||||||
import {useSelector} from "react-redux";
|
import {useSelector} from "react-redux";
|
||||||
import { postLogout, selectUserInfo } from "../app/loginSlice";
|
import { postLogout, selectUserInfo } from "../app/loginSlice";
|
||||||
import { useAppDispatch } from "../app/store";
|
import { useAppDispatch } from "../app/store";
|
||||||
import { AppAvatar } from "./appAvatar";
|
|
||||||
|
|
||||||
interface TopAppBarProps {
|
interface TopAppBarProps {
|
||||||
height: number;
|
height: number;
|
||||||
|
|
@ -39,73 +39,73 @@ function TopAppBar(props: TopAppBarProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppBar
|
<AppBar position="absolute" sx={{zIndex:1600, height: `${props.height}px` }}>
|
||||||
position="absolute"
|
<Toolbar disableGutters>
|
||||||
sx={{ zIndex: 1600, height: `${props.height}px` }}
|
<Typography
|
||||||
>
|
variant="h6"
|
||||||
<Toolbar disableGutters>
|
noWrap
|
||||||
<Typography
|
component="a"
|
||||||
variant="h6"
|
sx={{
|
||||||
noWrap
|
marginLeft: "1rem",
|
||||||
component="a"
|
mr: 2,
|
||||||
sx={{
|
display: "flex",
|
||||||
marginLeft: "1rem",
|
fontFamily: "monospace",
|
||||||
mr: 2,
|
fontWeight: 700,
|
||||||
display: "flex",
|
letterSpacing: ".3rem",
|
||||||
fontFamily: "monospace",
|
color: "inherit",
|
||||||
fontWeight: 700,
|
textDecoration: "none",
|
||||||
letterSpacing: ".3rem",
|
|
||||||
color: "inherit",
|
|
||||||
textDecoration: "none",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
DevSpace
|
|
||||||
</Typography>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
flexGrow: 1,
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "right",
|
|
||||||
padding: "1rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<NotificationBell />
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box sx={{ flexGrow: 0, mr: "2rem" }}>
|
|
||||||
<Tooltip title="Open settings">
|
|
||||||
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
|
|
||||||
<AppAvatar user={userInfo} />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
<Menu
|
|
||||||
sx={{ mt: "45px" }}
|
|
||||||
id="menu-appbar"
|
|
||||||
anchorEl={anchorElUser}
|
|
||||||
anchorOrigin={{
|
|
||||||
vertical: "top",
|
|
||||||
horizontal: "right",
|
|
||||||
}}
|
}}
|
||||||
keepMounted
|
|
||||||
transformOrigin={{
|
|
||||||
vertical: "top",
|
|
||||||
horizontal: "right",
|
|
||||||
}}
|
|
||||||
open={Boolean(anchorElUser)}
|
|
||||||
onClose={handleCloseUserMenu}
|
|
||||||
>
|
>
|
||||||
<MenuItem key={"profile"} onClick={handleCloseUserMenu}>
|
DevSpace
|
||||||
<Typography textAlign="center">{"Profile"}</Typography>
|
</Typography>
|
||||||
</MenuItem>
|
<Box
|
||||||
<MenuItem key={"settings"} onClick={handleCloseUserMenu}>
|
sx={{
|
||||||
<Typography textAlign="center">{"Settings"}</Typography>
|
flexGrow: 1,
|
||||||
</MenuItem>
|
display: "flex",
|
||||||
<MenuItem key={"logout"} onClick={handleLogout}>
|
justifyContent: "right",
|
||||||
<Typography textAlign="center">{"Logout"}</Typography>
|
padding: "1rem",
|
||||||
</MenuItem>
|
}}
|
||||||
</Menu>
|
>
|
||||||
</Box>
|
<NotificationBell />
|
||||||
</Toolbar>
|
</Box>
|
||||||
|
|
||||||
|
<Box sx={{ flexGrow: 0, mr:"2rem" }}>
|
||||||
|
<Tooltip title="Open settings" >
|
||||||
|
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
|
||||||
|
<Avatar
|
||||||
|
alt={`${userInfo.firstName} ${userInfo.lastName}`}
|
||||||
|
src="/static/images/avatar/2.jpg"
|
||||||
|
/>
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
<Menu
|
||||||
|
sx={{ mt: "45px" }}
|
||||||
|
id="menu-appbar"
|
||||||
|
anchorEl={anchorElUser}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: "top",
|
||||||
|
horizontal: "right",
|
||||||
|
}}
|
||||||
|
keepMounted
|
||||||
|
transformOrigin={{
|
||||||
|
vertical: "top",
|
||||||
|
horizontal: "right",
|
||||||
|
}}
|
||||||
|
open={Boolean(anchorElUser)}
|
||||||
|
onClose={handleCloseUserMenu}
|
||||||
|
>
|
||||||
|
<MenuItem key={"profile"} onClick={handleCloseUserMenu}>
|
||||||
|
<Typography textAlign="center">{"Profile"}</Typography>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key={"settings"} onClick={handleCloseUserMenu}>
|
||||||
|
<Typography textAlign="center">{"Settings"}</Typography>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem key={"logout"} onClick={handleLogout}>
|
||||||
|
<Typography textAlign="center">{"Logout"}</Typography>
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
</Box>
|
||||||
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,79 +1,3 @@
|
||||||
import { useSelector } from "react-redux";
|
|
||||||
import { getUsers, selectUsers } from "../app/usersSlice";
|
|
||||||
import React, { MouseEventHandler, useEffect } from "react";
|
|
||||||
import { useAppDispatch } from "../app/store";
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Divider,
|
|
||||||
List,
|
|
||||||
ListItem,
|
|
||||||
ListItemText,
|
|
||||||
Paper,
|
|
||||||
TextField,
|
|
||||||
} from "@mui/material";
|
|
||||||
import { AppAvatar } from "../components/appAvatar";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import { matchSorter } from "match-sorter";
|
|
||||||
|
|
||||||
export default function Search() {
|
export default function Search() {
|
||||||
const userList = useSelector(selectUsers);
|
return <>Search</>;
|
||||||
const [displayUserList, setDisplayUserList] = React.useState(userList);
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
dispatch(getUsers());
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const searchValue = event.currentTarget.value;
|
|
||||||
if (searchValue === "") {
|
|
||||||
setDisplayUserList(userList);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setDisplayUserList(
|
|
||||||
matchSorter(userList, searchValue, { keys: ["firstName", "lastName"] })
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClick: MouseEventHandler = (event) => {
|
|
||||||
const userId = event.currentTarget.id;
|
|
||||||
navigate(`/user/${userId}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
|
||||||
<Paper sx={{ mb: "2rem" }}>
|
|
||||||
<TextField
|
|
||||||
sx={{ width: "100%" }}
|
|
||||||
label="Search for a user..."
|
|
||||||
onChange={handleSearchChange}
|
|
||||||
/>
|
|
||||||
</Paper>
|
|
||||||
<Paper>
|
|
||||||
<List>
|
|
||||||
{displayUserList.map((user) => (
|
|
||||||
<>
|
|
||||||
<ListItem key={user.id}>
|
|
||||||
<Button
|
|
||||||
fullWidth
|
|
||||||
sx={{ textAlign: "start" }}
|
|
||||||
id={user.id?.toString()}
|
|
||||||
onClick={handleClick}
|
|
||||||
>
|
|
||||||
<AppAvatar user={user} />
|
|
||||||
<ListItemText
|
|
||||||
sx={{ ml: "2rem" }}
|
|
||||||
primary={`${user.firstName} ${user.lastName}`}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</ListItem>
|
|
||||||
<Divider sx={{ mb: "0.3rem" }} />
|
|
||||||
</>
|
|
||||||
))}
|
|
||||||
</List>
|
|
||||||
</Paper>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,7 @@ export class UserController {
|
||||||
user.deleteSensitiveFields();
|
user.deleteSensitiveFields();
|
||||||
});
|
});
|
||||||
|
|
||||||
// remove the users set to private
|
res.status(200).send(users);
|
||||||
const publicUsers = users.filter((user) => user.isPrivate === false);
|
|
||||||
|
|
||||||
res.status(200).send(publicUsers);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -70,7 +67,7 @@ export class UserController {
|
||||||
* $ref: '#/components/schemas/UserWithRelations'
|
* $ref: '#/components/schemas/UserWithRelations'
|
||||||
*/
|
*/
|
||||||
public getUser = catchAsync(
|
public getUser = catchAsync(
|
||||||
async (req: AppRequest, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const id = req.params.id;
|
const id = req.params.id;
|
||||||
const parsedId = parseInt(id);
|
const parsedId = parseInt(id);
|
||||||
// Check if ID is a number
|
// Check if ID is a number
|
||||||
|
|
@ -95,18 +92,6 @@ export class UserController {
|
||||||
);
|
);
|
||||||
user.followers.forEach((follower) => follower.deleteSensitiveFields());
|
user.followers.forEach((follower) => follower.deleteSensitiveFields());
|
||||||
|
|
||||||
// If the user is private, only return the user
|
|
||||||
// if the requesting user is following
|
|
||||||
const followStatus = user.followers.some(
|
|
||||||
(follower) => follower.id === req.user.id
|
|
||||||
);
|
|
||||||
if (user.isPrivate && !followStatus) {
|
|
||||||
user.followed = [];
|
|
||||||
user.followers = [];
|
|
||||||
user.posts = [];
|
|
||||||
user.comments = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.send(user);
|
return res.send(user);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -150,60 +135,6 @@ export class UserController {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /users/me:
|
|
||||||
* patch:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags:
|
|
||||||
* - Users
|
|
||||||
* summary: Update the currently logged in user
|
|
||||||
* requestBody:
|
|
||||||
* required: true
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* type: object
|
|
||||||
* properties:
|
|
||||||
* isPrivate:
|
|
||||||
* type: boolean
|
|
||||||
* description: Whether the user's account is private
|
|
||||||
* profilePictureId:
|
|
||||||
* type: string
|
|
||||||
* description: The ID of the user's profile picture
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: Successfully updated the user
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* $ref: '#/components/schemas/UserWithRelationsAndNotifications'
|
|
||||||
* 400:
|
|
||||||
* description: Invalid request body
|
|
||||||
*/
|
|
||||||
public updateMe = catchAsync(
|
|
||||||
async (req: AppRequest, res: Response, next: NextFunction) => {
|
|
||||||
const user = await this.userRepository.findOne({
|
|
||||||
where: { id: req.user.id },
|
|
||||||
relations: {
|
|
||||||
followed: true,
|
|
||||||
followers: true,
|
|
||||||
posts: true,
|
|
||||||
comments: true,
|
|
||||||
notifications: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
user.isPrivate = req.body.isPrivate || user.isPrivate;
|
|
||||||
user.profilePictureId =
|
|
||||||
req.body.profilePictureId || user.profilePictureId;
|
|
||||||
await this.userRepository.save(user);
|
|
||||||
|
|
||||||
return res.status(200).send(user);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /users/{id}/follow:
|
* /users/{id}/follow:
|
||||||
|
|
|
||||||
|
|
@ -6,57 +6,53 @@ import {Notification} from "./Notification";
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class User {
|
export class User {
|
||||||
@PrimaryGeneratedColumn()
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@Column()
|
@PrimaryGeneratedColumn()
|
||||||
firstName: string;
|
id: number
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
lastName: string;
|
firstName: string
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
email: string;
|
lastName: string
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
password: string;
|
email: string
|
||||||
|
|
||||||
@Column({ default: false })
|
@Column()
|
||||||
isPrivate: boolean;
|
password: string
|
||||||
|
|
||||||
@Column({ default: null })
|
@ManyToMany(type => User)
|
||||||
profilePictureId: string;
|
@JoinTable()
|
||||||
|
followed: User[]
|
||||||
|
|
||||||
@ManyToMany((type) => User)
|
@ManyToMany(type => User)
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
followed: User[];
|
followers: User[]
|
||||||
|
|
||||||
@ManyToMany((type) => User)
|
@OneToMany(type => Post, post=> post.createdBy)
|
||||||
@JoinTable()
|
posts: Post[]
|
||||||
followers: User[];
|
|
||||||
|
|
||||||
@OneToMany((type) => Post, (post) => post.createdBy)
|
@OneToMany(type => Comment, comment=> comment.createdBy)
|
||||||
posts: Post[];
|
comments: Comment[]
|
||||||
|
|
||||||
@OneToMany((type) => Comment, (comment) => comment.createdBy)
|
@OneToMany(type => Notification, notification => notification.belongsTo)
|
||||||
comments: Comment[];
|
notifications: Notification[]
|
||||||
|
|
||||||
@OneToMany((type) => Notification, (notification) => notification.belongsTo)
|
|
||||||
notifications: Notification[];
|
|
||||||
|
|
||||||
static async hashPassword(password: string) {
|
static async hashPassword(password: string){
|
||||||
return await bcrypt.hash(password, parseInt(process.env.SALT_ROUNDS));
|
return await bcrypt.hash(password, parseInt(process.env.SALT_ROUNDS))
|
||||||
}
|
}
|
||||||
|
|
||||||
async comparePassword(password: string) {
|
async comparePassword(password: string){
|
||||||
return await bcrypt.compare(password, this.password);
|
return await bcrypt.compare(password, this.password)
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteSensitiveFields() {
|
public deleteSensitiveFields(){
|
||||||
delete this.password;
|
delete this.password
|
||||||
delete this.email;
|
delete this.email
|
||||||
delete this.notifications;
|
delete this.notifications
|
||||||
delete this.followed;
|
delete this.followed
|
||||||
delete this.followers;
|
delete this.followers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,14 +115,6 @@ const swaggerOptions = {
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "User first name",
|
description: "User first name",
|
||||||
},
|
},
|
||||||
isPrivate: {
|
|
||||||
type: "boolean",
|
|
||||||
description: "User private status",
|
|
||||||
},
|
|
||||||
profilePictureId: {
|
|
||||||
type: "string",
|
|
||||||
description: "User profile picture ID",
|
|
||||||
},
|
|
||||||
lastName: {
|
lastName: {
|
||||||
type: "string",
|
type: "string",
|
||||||
description: "User last name",
|
description: "User last name",
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,7 @@ export const UserRoutes = Router();
|
||||||
const userController = new UserController()
|
const userController = new UserController()
|
||||||
|
|
||||||
UserRoutes.route("/").get(userController.getAllUsers)
|
UserRoutes.route("/").get(userController.getAllUsers)
|
||||||
UserRoutes.route("/me")
|
UserRoutes.route("/me").get(userController.getMe)
|
||||||
.get(userController.getMe)
|
|
||||||
.patch(userController.updateMe);
|
|
||||||
UserRoutes.route("/:id").get(userController.getUser)
|
UserRoutes.route("/:id").get(userController.getUser)
|
||||||
UserRoutes.route("/follow/:id")
|
UserRoutes.route("/follow/:id")
|
||||||
.post(userController.followUser)
|
.post(userController.followUser)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue