parent
e6f73932fa
commit
d73bd1dd04
1394
DevSpaceOApi.json
1394
DevSpaceOApi.json
File diff suppressed because it is too large
Load Diff
|
|
@ -1406,40 +1406,6 @@ export class PostsApi extends BaseAPI {
|
||||||
*/
|
*/
|
||||||
export const UsersApiAxiosParamCreator = function (configuration?: Configuration) {
|
export const UsersApiAxiosParamCreator = function (configuration?: Configuration) {
|
||||||
return {
|
return {
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @summary Get all users
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
usersGet: async (options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
|
||||||
const localVarPath = `/users`;
|
|
||||||
// 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: 'GET', ...baseOptions, ...options};
|
|
||||||
const localVarHeaderParameter = {} as any;
|
|
||||||
const localVarQueryParameter = {} as any;
|
|
||||||
|
|
||||||
// authentication bearerAuth required
|
|
||||||
// http bearer authentication required
|
|
||||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
|
||||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
|
||||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: toPathString(localVarUrlObj),
|
|
||||||
options: localVarRequestOptions,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary Unfollow a user
|
* @summary Unfollow a user
|
||||||
|
|
@ -1447,10 +1413,10 @@ export const UsersApiAxiosParamCreator = function (configuration?: Configuration
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
usersIdFollowDelete: async (id: number, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
usersFollowIdDelete: async (id: number, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
// verify required parameter 'id' is not null or undefined
|
// verify required parameter 'id' is not null or undefined
|
||||||
assertParamExists('usersIdFollowDelete', 'id', id)
|
assertParamExists('usersFollowIdDelete', 'id', id)
|
||||||
const localVarPath = `/users/{id}/follow`
|
const localVarPath = `/users/follow/{id}`
|
||||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
|
@ -1485,10 +1451,10 @@ export const UsersApiAxiosParamCreator = function (configuration?: Configuration
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
usersIdFollowPost: async (id: number, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
usersFollowIdPost: async (id: number, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
// verify required parameter 'id' is not null or undefined
|
// verify required parameter 'id' is not null or undefined
|
||||||
assertParamExists('usersIdFollowPost', 'id', id)
|
assertParamExists('usersFollowIdPost', 'id', id)
|
||||||
const localVarPath = `/users/{id}/follow`
|
const localVarPath = `/users/follow/{id}`
|
||||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
|
@ -1507,6 +1473,40 @@ export const UsersApiAxiosParamCreator = function (configuration?: Configuration
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Get all users
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
usersGet: async (options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
const localVarPath = `/users`;
|
||||||
|
// 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: 'GET', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
// authentication bearerAuth required
|
||||||
|
// http bearer authentication required
|
||||||
|
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
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};
|
||||||
|
|
@ -1638,18 +1638,6 @@ export const UsersApiAxiosParamCreator = function (configuration?: Configuration
|
||||||
export const UsersApiFp = function(configuration?: Configuration) {
|
export const UsersApiFp = function(configuration?: Configuration) {
|
||||||
const localVarAxiosParamCreator = UsersApiAxiosParamCreator(configuration)
|
const localVarAxiosParamCreator = UsersApiAxiosParamCreator(configuration)
|
||||||
return {
|
return {
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @summary Get all users
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
async usersGet(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<User>>> {
|
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.usersGet(options);
|
|
||||||
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
|
||||||
const localVarOperationServerBasePath = operationServerMap['UsersApi.usersGet']?.[localVarOperationServerIndex]?.url;
|
|
||||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary Unfollow a user
|
* @summary Unfollow a user
|
||||||
|
|
@ -1657,10 +1645,10 @@ export const UsersApiFp = function(configuration?: Configuration) {
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
async usersIdFollowDelete(id: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserWithRelations>> {
|
async usersFollowIdDelete(id: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserWithRelations>> {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.usersIdFollowDelete(id, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.usersFollowIdDelete(id, options);
|
||||||
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||||
const localVarOperationServerBasePath = operationServerMap['UsersApi.usersIdFollowDelete']?.[localVarOperationServerIndex]?.url;
|
const localVarOperationServerBasePath = operationServerMap['UsersApi.usersFollowIdDelete']?.[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);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
|
@ -1670,10 +1658,22 @@ export const UsersApiFp = function(configuration?: Configuration) {
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
async usersIdFollowPost(id: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserWithRelations>> {
|
async usersFollowIdPost(id: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserWithRelations>> {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.usersIdFollowPost(id, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.usersFollowIdPost(id, options);
|
||||||
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||||
const localVarOperationServerBasePath = operationServerMap['UsersApi.usersIdFollowPost']?.[localVarOperationServerIndex]?.url;
|
const localVarOperationServerBasePath = operationServerMap['UsersApi.usersFollowIdPost']?.[localVarOperationServerIndex]?.url;
|
||||||
|
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Get all users
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async usersGet(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<User>>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.usersGet(options);
|
||||||
|
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||||
|
const localVarOperationServerBasePath = operationServerMap['UsersApi.usersGet']?.[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);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
|
@ -1724,15 +1724,6 @@ export const UsersApiFp = function(configuration?: Configuration) {
|
||||||
export const UsersApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
export const UsersApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
||||||
const localVarFp = UsersApiFp(configuration)
|
const localVarFp = UsersApiFp(configuration)
|
||||||
return {
|
return {
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @summary Get all users
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
usersGet(options?: any): AxiosPromise<Array<User>> {
|
|
||||||
return localVarFp.usersGet(options).then((request) => request(axios, basePath));
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary Unfollow a user
|
* @summary Unfollow a user
|
||||||
|
|
@ -1740,8 +1731,8 @@ export const UsersApiFactory = function (configuration?: Configuration, basePath
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
usersIdFollowDelete(id: number, options?: any): AxiosPromise<UserWithRelations> {
|
usersFollowIdDelete(id: number, options?: any): AxiosPromise<UserWithRelations> {
|
||||||
return localVarFp.usersIdFollowDelete(id, options).then((request) => request(axios, basePath));
|
return localVarFp.usersFollowIdDelete(id, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
@ -1750,8 +1741,17 @@ export const UsersApiFactory = function (configuration?: Configuration, basePath
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
usersIdFollowPost(id: number, options?: any): AxiosPromise<UserWithRelations> {
|
usersFollowIdPost(id: number, options?: any): AxiosPromise<UserWithRelations> {
|
||||||
return localVarFp.usersIdFollowPost(id, options).then((request) => request(axios, basePath));
|
return localVarFp.usersFollowIdPost(id, options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Get all users
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
usersGet(options?: any): AxiosPromise<Array<User>> {
|
||||||
|
return localVarFp.usersGet(options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
@ -1792,17 +1792,6 @@ export const UsersApiFactory = function (configuration?: Configuration, basePath
|
||||||
* @extends {BaseAPI}
|
* @extends {BaseAPI}
|
||||||
*/
|
*/
|
||||||
export class UsersApi extends BaseAPI {
|
export class UsersApi extends BaseAPI {
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @summary Get all users
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
* @memberof UsersApi
|
|
||||||
*/
|
|
||||||
public usersGet(options?: RawAxiosRequestConfig) {
|
|
||||||
return UsersApiFp(this.configuration).usersGet(options).then((request) => request(this.axios, this.basePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @summary Unfollow a user
|
* @summary Unfollow a user
|
||||||
|
|
@ -1811,8 +1800,8 @@ export class UsersApi extends BaseAPI {
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
* @memberof UsersApi
|
* @memberof UsersApi
|
||||||
*/
|
*/
|
||||||
public usersIdFollowDelete(id: number, options?: RawAxiosRequestConfig) {
|
public usersFollowIdDelete(id: number, options?: RawAxiosRequestConfig) {
|
||||||
return UsersApiFp(this.configuration).usersIdFollowDelete(id, options).then((request) => request(this.axios, this.basePath));
|
return UsersApiFp(this.configuration).usersFollowIdDelete(id, options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1823,8 +1812,19 @@ export class UsersApi extends BaseAPI {
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
* @memberof UsersApi
|
* @memberof UsersApi
|
||||||
*/
|
*/
|
||||||
public usersIdFollowPost(id: number, options?: RawAxiosRequestConfig) {
|
public usersFollowIdPost(id: number, options?: RawAxiosRequestConfig) {
|
||||||
return UsersApiFp(this.configuration).usersIdFollowPost(id, options).then((request) => request(this.axios, this.basePath));
|
return UsersApiFp(this.configuration).usersFollowIdPost(id, options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Get all users
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof UsersApi
|
||||||
|
*/
|
||||||
|
public usersGet(options?: RawAxiosRequestConfig) {
|
||||||
|
return UsersApiFp(this.configuration).usersGet(options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ interface loginState {
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
jwt: string;
|
jwt: string;
|
||||||
|
id: number;
|
||||||
|
profilePictureId: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,6 +30,8 @@ const initialState: loginState = {
|
||||||
firstName: "",
|
firstName: "",
|
||||||
lastName: "",
|
lastName: "",
|
||||||
jwt: "",
|
jwt: "",
|
||||||
|
id: -1,
|
||||||
|
profilePictureId: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -37,9 +41,11 @@ export const loginSlice = createSlice({
|
||||||
reducers: {
|
reducers: {
|
||||||
login: (state, action) => {
|
login: (state, action) => {
|
||||||
state.loggedIn = true;
|
state.loggedIn = true;
|
||||||
|
state.userInfo.id = action.payload.id;
|
||||||
state.userInfo.jwt = action.payload.jwt;
|
state.userInfo.jwt = action.payload.jwt;
|
||||||
state.userInfo.firstName = action.payload.firstName;
|
state.userInfo.firstName = action.payload.firstName;
|
||||||
state.userInfo.lastName = action.payload.lastName;
|
state.userInfo.lastName = action.payload.lastName;
|
||||||
|
state.userInfo.profilePictureId = action.payload.profilePictureId;
|
||||||
},
|
},
|
||||||
logoff: (state) => {
|
logoff: (state) => {
|
||||||
state.loggedIn = false;
|
state.loggedIn = false;
|
||||||
|
|
@ -85,6 +91,8 @@ export const postLogin =
|
||||||
jwt: response.data.token,
|
jwt: response.data.token,
|
||||||
firstName: userResponse.data.firstName,
|
firstName: userResponse.data.firstName,
|
||||||
lastName: userResponse.data.lastName,
|
lastName: userResponse.data.lastName,
|
||||||
|
id: userResponse.data.id,
|
||||||
|
profilePictureId: userResponse.data.profilePictureId,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
dispatch(setStatus(Status.succeeded));
|
dispatch(setStatus(Status.succeeded));
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ export const store = configureStore({
|
||||||
post: postReducer,
|
post: postReducer,
|
||||||
users: usersReducer,
|
users: usersReducer,
|
||||||
},
|
},
|
||||||
|
devTools: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AppDispatch = typeof store.dispatch;
|
export type AppDispatch = typeof store.dispatch;
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ export const getUserWithRelations =
|
||||||
dispatch(setStatus(Status.loading));
|
dispatch(setStatus(Status.loading));
|
||||||
try {
|
try {
|
||||||
const response = await api.usersIdGet(userId);
|
const response = await api.usersIdGet(userId);
|
||||||
dispatch(setUserWithRelations(response));
|
dispatch(setUserWithRelations(response.data));
|
||||||
dispatch(setStatus(Status.idle));
|
dispatch(setStatus(Status.idle));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
dispatch(setError((error as Error).message));
|
dispatch(setError((error as Error).message));
|
||||||
|
|
@ -73,10 +73,12 @@ export const followUser =
|
||||||
|
|
||||||
dispatch(setStatus(Status.loading));
|
dispatch(setStatus(Status.loading));
|
||||||
try {
|
try {
|
||||||
await api.usersIdFollowPost(userId);
|
console.log("Trying to follow: ", userId);
|
||||||
dispatch(getUsers());
|
await api.usersFollowIdPost(userId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
dispatch(setError((error as Error).message));
|
dispatch(setError((error as Error).message));
|
||||||
|
} finally {
|
||||||
|
dispatch(getUserWithRelations(userId));
|
||||||
dispatch(setStatus(Status.idle));
|
dispatch(setStatus(Status.idle));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -88,10 +90,12 @@ export const unFollowUser =
|
||||||
|
|
||||||
dispatch(setStatus(Status.loading));
|
dispatch(setStatus(Status.loading));
|
||||||
try {
|
try {
|
||||||
await api.usersIdFollowDelete(userId);
|
console.log("Trying to unfollow: ", userId);
|
||||||
dispatch(getUsers());
|
await api.usersFollowIdDelete(userId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
dispatch(setError((error as Error).message));
|
dispatch(setError((error as Error).message));
|
||||||
|
} finally {
|
||||||
|
dispatch(getUserWithRelations(userId));
|
||||||
dispatch(setStatus(Status.idle));
|
dispatch(setStatus(Status.idle));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -28,31 +28,31 @@ export default function PostListItem(props: PostListItemProps) {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const userInfo = useSelector(selectUserInfo);
|
const userInfo = useSelector(selectUserInfo);
|
||||||
const [liked, setLiked] = useState(false);
|
const [liked, setLiked] = useState(
|
||||||
const [numberOfLikes, setNumberOfLikes] = useState(0);
|
props.post.likedBy?.some((user) => user.id === userInfo.id) || false
|
||||||
|
);
|
||||||
|
const [numberOfLikes, setNumberOfLikes] = useState(
|
||||||
|
props.post.likedBy?.length || 0
|
||||||
|
);
|
||||||
|
|
||||||
// On component mount, set the number of likes
|
|
||||||
// and whether the user has liked the post
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setLiked(
|
||||||
|
props.post.likedBy?.some((user) => user.id === userInfo.id) || false
|
||||||
|
);
|
||||||
setNumberOfLikes(props.post.likedBy?.length || 0);
|
setNumberOfLikes(props.post.likedBy?.length || 0);
|
||||||
setLiked(props.post.likedBy?.includes(userInfo) || false);
|
}, [props.post.likedBy, userInfo.id]);
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleLike = () => {
|
const handleLike = () => {
|
||||||
if (!liked) {
|
if (!liked) {
|
||||||
// Instant feedback to the user
|
|
||||||
setLiked(true);
|
|
||||||
setNumberOfLikes(numberOfLikes + 1);
|
setNumberOfLikes(numberOfLikes + 1);
|
||||||
// Dispatch the call to the API
|
|
||||||
dispatch(likePost(props.post.id as number));
|
dispatch(likePost(props.post.id as number));
|
||||||
}
|
}
|
||||||
// If the user has already liked the post, unlike it
|
// If the user has already liked the post, unlike it
|
||||||
else {
|
else {
|
||||||
setLiked(false);
|
|
||||||
setNumberOfLikes(numberOfLikes - 1);
|
setNumberOfLikes(numberOfLikes - 1);
|
||||||
|
|
||||||
dispatch(unLikePost(props.post.id as number));
|
dispatch(unLikePost(props.post.id as number));
|
||||||
}
|
}
|
||||||
|
setLiked((prevState) => !prevState);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePostClick = () => {
|
const handlePostClick = () => {
|
||||||
|
|
@ -72,7 +72,13 @@ export default function PostListItem(props: PostListItemProps) {
|
||||||
alignItems: "start",
|
alignItems: "start",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button onClick={handleUserClick} sx={{ textTransform: "none" }}>
|
<Button
|
||||||
|
onClick={handleUserClick}
|
||||||
|
sx={{
|
||||||
|
textTransform: "none",
|
||||||
|
display: `${props.postType === "user" ? "none" : "block"}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Typography
|
<Typography
|
||||||
gutterBottom
|
gutterBottom
|
||||||
sx={{ textAlign: "left", width: "100%" }}
|
sx={{ textAlign: "left", width: "100%" }}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ const root = ReactDOM.createRoot(
|
||||||
|
|
||||||
const defaultTheme = createTheme({
|
const defaultTheme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
mode: "dark",
|
mode: "light",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,14 @@ import {
|
||||||
fetchGlobalPosts,
|
fetchGlobalPosts,
|
||||||
selectAllPosts,
|
selectAllPosts,
|
||||||
selectFollowedPosts,
|
selectFollowedPosts,
|
||||||
|
selectStatus,
|
||||||
} from "../app/postSlice";
|
} from "../app/postSlice";
|
||||||
import { useAppDispatch } from "../app/store";
|
import { store, useAppDispatch } from "../app/store";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Box, List, Typography } from "@mui/material";
|
import { Box, CircularProgress, List, Typography } from "@mui/material";
|
||||||
import { Post } from "../api";
|
import { Post } from "../api";
|
||||||
import PostListItem from "../components/postListItem";
|
import PostListItem from "../components/postListItem";
|
||||||
|
import { Status } from "../util/types";
|
||||||
|
|
||||||
interface PostListProps {
|
interface PostListProps {
|
||||||
type: "all" | "user";
|
type: "all" | "user";
|
||||||
|
|
@ -19,12 +21,18 @@ export default function PostList(props: PostListProps) {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const followedPosts = useSelector(selectFollowedPosts);
|
const followedPosts = useSelector(selectFollowedPosts);
|
||||||
const globalPosts = useSelector(selectAllPosts);
|
const globalPosts = useSelector(selectAllPosts);
|
||||||
|
const status = useSelector(selectStatus);
|
||||||
|
const [posts, setPosts] = useState<Post[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.type === "all") dispatch(fetchGlobalPosts());
|
dispatch(fetchGlobalPosts());
|
||||||
if (props.type === "user") dispatch(fetchFollowedPosts());
|
dispatch(fetchFollowedPosts());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPosts(props.type === "all" ? globalPosts : followedPosts);
|
||||||
|
}, [followedPosts, globalPosts, props.type]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
|
@ -34,39 +42,48 @@ export default function PostList(props: PostListProps) {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.type === "all" && (
|
{status === Status.loading ? (
|
||||||
<List>
|
<CircularProgress />
|
||||||
{globalPosts.map((post: Post) => (
|
) : (
|
||||||
<PostListItem post={post} postType="all" key={post.id} />
|
<>
|
||||||
))}
|
{props.type === "all" && (
|
||||||
</List>
|
<List>
|
||||||
)}
|
{posts.map((post: Post) => (
|
||||||
{props.type === "user" && (
|
<PostListItem post={post} postType="all" key={post.id} />
|
||||||
<List>
|
))}
|
||||||
{followedPosts.map((post: Post) => (
|
</List>
|
||||||
<PostListItem post={post} postType="user" key={post.id} />
|
|
||||||
))}
|
|
||||||
{followedPosts.length === 0 && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="h3"
|
|
||||||
sx={{ color: "text.secondary", mb: "2rem" }}
|
|
||||||
>
|
|
||||||
Whops!
|
|
||||||
</Typography>
|
|
||||||
<Typography textAlign="center" sx={{ color: "text.secondary" }}>
|
|
||||||
There is no content here! Try following some users, or check the
|
|
||||||
global feed
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
</List>
|
{props.type === "user" && (
|
||||||
|
<List>
|
||||||
|
{followedPosts.map((post: Post) => (
|
||||||
|
<PostListItem post={post} postType="user" key={post.id} />
|
||||||
|
))}
|
||||||
|
{followedPosts.length === 0 && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="h3"
|
||||||
|
sx={{ color: "text.secondary", mb: "2rem" }}
|
||||||
|
>
|
||||||
|
Whops!
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
textAlign="center"
|
||||||
|
sx={{ color: "text.secondary" }}
|
||||||
|
>
|
||||||
|
There is no content here! Try following some users, or check
|
||||||
|
the global feed
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,155 @@
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
CircularProgress,
|
||||||
|
List,
|
||||||
|
Paper,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
|
import { AppAvatar } from "../components/appAvatar";
|
||||||
|
import { useAppDispatch } from "../app/store";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import {
|
||||||
|
followUser,
|
||||||
|
getUserWithRelations,
|
||||||
|
selectAUser,
|
||||||
|
selectStatus,
|
||||||
|
unFollowUser,
|
||||||
|
} from "../app/usersSlice";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { selectUserInfo } from "../app/loginSlice";
|
||||||
|
import PostListItem from "../components/postListItem";
|
||||||
|
import { Post, UserWithRelations } from "../api";
|
||||||
|
import React from "react";
|
||||||
|
import { Status } from "../util/types";
|
||||||
|
|
||||||
export interface ProfileProps {
|
export interface ProfileProps {
|
||||||
selfProfile?: boolean;
|
selfProfile?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Profile(props: ProfileProps) {
|
export default function Profile(props: ProfileProps) {
|
||||||
const { selfProfile } = props;
|
const userId = useParams<{ id: string }>().id;
|
||||||
const userId = useParams<{ userId: string }>().userId;
|
const [userToDisplay, setUserToDisplay] =
|
||||||
|
React.useState<UserWithRelations | null>();
|
||||||
|
const aUser = useSelector(selectAUser);
|
||||||
|
const [isFollowed, setIsFollowed] = React.useState(false);
|
||||||
|
const status = useSelector(selectStatus);
|
||||||
|
const selfUser = useSelector(selectUserInfo);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
return <>Profile</>;
|
useEffect(() => {
|
||||||
|
if (!props.selfProfile && userId) {
|
||||||
|
dispatch(getUserWithRelations(parseInt(userId, 10)));
|
||||||
|
} else {
|
||||||
|
dispatch(getUserWithRelations(selfUser.id));
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setUserToDisplay(aUser);
|
||||||
|
if (!props.selfProfile) {
|
||||||
|
setIsFollowed(
|
||||||
|
aUser?.followers?.some((user) => user.id === selfUser.id) || false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [aUser, props.selfProfile, selfUser.id]);
|
||||||
|
|
||||||
|
const handleFollow = () => {
|
||||||
|
if (isFollowed) {
|
||||||
|
dispatch(unFollowUser(userToDisplay!.id!));
|
||||||
|
} else {
|
||||||
|
dispatch(followUser(userToDisplay!.id!));
|
||||||
|
}
|
||||||
|
setIsFollowed((prevState) => !prevState);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{status === Status.loading && (
|
||||||
|
<>
|
||||||
|
<Typography variant="h3">Loading...</Typography>
|
||||||
|
<CircularProgress />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{userToDisplay && status === Status.idle && (
|
||||||
|
<>
|
||||||
|
<Paper
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
padding: "1rem 1rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AppAvatar user={userToDisplay!} />
|
||||||
|
<Typography variant="h4" sx={{ ml: "0.8rem" }}>
|
||||||
|
{userToDisplay?.firstName} {userToDisplay?.lastName}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: "flex", padding: "1rem 1rem" }}>
|
||||||
|
<Box sx={{ display: "flex" }}>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
sx={{ ml: "0.8rem", mr: "0.5rem" }}
|
||||||
|
>
|
||||||
|
Following:
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h6">
|
||||||
|
{userToDisplay?.followed?.length || 0}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: "flex" }}>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
sx={{ ml: "0.8rem", mr: "0.5rem" }}
|
||||||
|
>
|
||||||
|
Followers:{" "}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h6">
|
||||||
|
{userToDisplay?.followers?.length || 0}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: `${props.selfProfile ? "none" : "initial"}`,
|
||||||
|
ml: "0.8rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant={isFollowed ? "outlined" : "contained"}
|
||||||
|
onClick={handleFollow}
|
||||||
|
>
|
||||||
|
{isFollowed ? "Unfollow" : "Follow"}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
<List sx={{ width: "75%" }}>
|
||||||
|
{userToDisplay!.posts?.map((post: Post) => (
|
||||||
|
<PostListItem post={post} postType="user" key={post.id} />
|
||||||
|
)) || (
|
||||||
|
<Typography variant="h3">
|
||||||
|
This user has no posts yet!
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,10 +22,17 @@ export default function Search() {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// Dispatch the getUsers actions, and set it as the displayUserList
|
||||||
|
// once on the first render
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(getUsers());
|
dispatch(getUsers());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDisplayUserList(userList);
|
||||||
|
}, [userList]);
|
||||||
|
|
||||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const searchValue = event.currentTarget.value;
|
const searchValue = event.currentTarget.value;
|
||||||
if (searchValue === "") {
|
if (searchValue === "") {
|
||||||
|
|
@ -39,6 +46,7 @@ export default function Search() {
|
||||||
|
|
||||||
const handleClick: MouseEventHandler = (event) => {
|
const handleClick: MouseEventHandler = (event) => {
|
||||||
const userId = event.currentTarget.id;
|
const userId = event.currentTarget.id;
|
||||||
|
console.log("User Id from the search component: ", userId);
|
||||||
navigate(`/user/${userId}`);
|
navigate(`/user/${userId}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ export class UserController {
|
||||||
relations: {
|
relations: {
|
||||||
followed: true,
|
followed: true,
|
||||||
followers: true,
|
followers: true,
|
||||||
posts: true,
|
posts: { likedBy: true },
|
||||||
comments: true,
|
comments: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -89,17 +89,36 @@ export class UserController {
|
||||||
if (!user) return next(new AppError("No user found with that ID", 404));
|
if (!user) return next(new AppError("No user found with that ID", 404));
|
||||||
|
|
||||||
// remove sensitive fields
|
// remove sensitive fields
|
||||||
user.deleteSensitiveFields();
|
user.deletePrivateFields();
|
||||||
user.followed.forEach((followedUser) =>
|
if (user.posts) {
|
||||||
followedUser.deleteSensitiveFields()
|
user.posts.forEach((post) => {
|
||||||
);
|
if (post.likedBy) {
|
||||||
user.followers.forEach((follower) => follower.deleteSensitiveFields());
|
post.likedBy.forEach((user) => user.deleteSensitiveFields());
|
||||||
|
}
|
||||||
|
if (post.comments) {
|
||||||
|
post.comments.forEach((comment) =>
|
||||||
|
comment.createdBy.deleteSensitiveFields()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (user.followed) {
|
||||||
|
user.followed.forEach((followedUser) =>
|
||||||
|
followedUser.deleteSensitiveFields()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let followStatus = false;
|
||||||
|
if (user.followers) {
|
||||||
|
user.followers.forEach((follower) => follower.deleteSensitiveFields());
|
||||||
|
// Set the followed status of the user
|
||||||
|
followStatus = user.followers.some(
|
||||||
|
(follower) => follower.id === req.user.id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// If the user is private, only return the user
|
// If the user is private, only return the user
|
||||||
// if the requesting user is following
|
// if the requesting user is following
|
||||||
const followStatus = user.followers.some(
|
|
||||||
(follower) => follower.id === req.user.id
|
|
||||||
);
|
|
||||||
if (user.isPrivate && !followStatus) {
|
if (user.isPrivate && !followStatus) {
|
||||||
user.followed = [];
|
user.followed = [];
|
||||||
user.followers = [];
|
user.followers = [];
|
||||||
|
|
@ -206,7 +225,7 @@ export class UserController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /users/{id}/follow:
|
* /users/follow/{id}:
|
||||||
* post:
|
* post:
|
||||||
* security:
|
* security:
|
||||||
* - bearerAuth: []
|
* - bearerAuth: []
|
||||||
|
|
@ -281,7 +300,7 @@ export class UserController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /users/{id}/follow:
|
* /users/follow/{id}:
|
||||||
* delete:
|
* delete:
|
||||||
* security:
|
* security:
|
||||||
* - bearerAuth: []
|
* - bearerAuth: []
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,13 @@ import {AppError} from "../util/AppError";
|
||||||
import {AppRequest} from "../util/AppRequest";
|
import {AppRequest} from "../util/AppRequest";
|
||||||
|
|
||||||
export class PostController {
|
export class PostController {
|
||||||
private postRepository = AppDataSource.getRepository(Post)
|
private postRepository = AppDataSource.getRepository(Post);
|
||||||
|
|
||||||
private commentRepository = AppDataSource.getRepository(Comment)
|
private commentRepository = AppDataSource.getRepository(Comment);
|
||||||
|
|
||||||
private notificationRepository = AppDataSource.getRepository(Notification)
|
private notificationRepository = AppDataSource.getRepository(Notification);
|
||||||
|
|
||||||
private userRepository = AppDataSource.getRepository(User)
|
private userRepository = AppDataSource.getRepository(User);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
|
|
@ -36,477 +36,523 @@ export class PostController {
|
||||||
* items:
|
* items:
|
||||||
* $ref: '#/components/schemas/Post'
|
* $ref: '#/components/schemas/Post'
|
||||||
*/
|
*/
|
||||||
public getAllPosts = catchAsync(async (_req, res, _next) => {
|
public getAllPosts = catchAsync(async (req: AppRequest, res, _next) => {
|
||||||
const posts = await this.postRepository.find({
|
const posts = await this.postRepository.find({
|
||||||
relations: { createdBy: true, likedBy: true, comments: {createdBy: true} },
|
relations: {
|
||||||
|
createdBy: true,
|
||||||
|
likedBy: true,
|
||||||
|
comments: { createdBy: true },
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove sensitive fields
|
// Remove sensitive fields
|
||||||
posts.forEach(post => {
|
posts.forEach((post) => {
|
||||||
post.deleteSensitiveFields()
|
post.deleteSensitiveFields();
|
||||||
})
|
});
|
||||||
|
// Remove private, non followed users
|
||||||
|
const filteredPosts = posts.filter((post) => {
|
||||||
|
let followStatus = false;
|
||||||
|
|
||||||
res.status(200).send(posts);
|
if(post.createdBy.followers){
|
||||||
|
followStatus = post.createdBy.followers.some(
|
||||||
|
(follower) => follower.id === req.user.id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (post.createdBy.isPrivate && !followStatus) {
|
||||||
|
return post.createdBy.followers.some(
|
||||||
|
(follower) => follower.id === req.user.id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(200).send(filteredPosts);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /posts/{id}:
|
* /posts/{id}:
|
||||||
* get:
|
* get:
|
||||||
* security:
|
* security:
|
||||||
* - bearerAuth: []
|
* - bearerAuth: []
|
||||||
* tags:
|
* tags:
|
||||||
* - Posts
|
* - Posts
|
||||||
* operationId: getPost
|
* operationId: getPost
|
||||||
* summary: Get a post by ID
|
* summary: Get a post by ID
|
||||||
* parameters:
|
* parameters:
|
||||||
* - in: path
|
* - in: path
|
||||||
* name: id
|
* name: id
|
||||||
* required: true
|
* required: true
|
||||||
* description: ID of the post
|
* description: ID of the post
|
||||||
* schema:
|
* schema:
|
||||||
* type: integer
|
* type: integer
|
||||||
* responses:
|
* responses:
|
||||||
* 200:
|
* 200:
|
||||||
* description: A post
|
* description: A post
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
* $ref: '#/components/schemas/Post'
|
* $ref: '#/components/schemas/Post'
|
||||||
* 400:
|
* 400:
|
||||||
* description: Invalid ID
|
* description: Invalid ID
|
||||||
* 404:
|
* 404:
|
||||||
* description: No post found with that ID
|
* description: No post found with that ID
|
||||||
*/
|
*/
|
||||||
public getPost = catchAsync(async (req, res, next) => {
|
public getPost = catchAsync(async (req, res, next) => {
|
||||||
const { post, errorMessage } = await this.validateRequestAndGetEntities(
|
const { post, errorMessage } = await this.validateRequestAndGetEntities(
|
||||||
req
|
req
|
||||||
);
|
);
|
||||||
|
|
||||||
if(errorMessage == 'Invalid ID') return next(new AppError('Invalid ID', 400))
|
if (errorMessage == "Invalid ID")
|
||||||
if(errorMessage == 'No post found with that ID'){
|
return next(new AppError("Invalid ID", 400));
|
||||||
return next(new AppError('No post found with that ID', 404))
|
if (errorMessage == "No post found with that ID") {
|
||||||
}
|
return next(new AppError("No post found with that ID", 404));
|
||||||
|
}
|
||||||
|
|
||||||
// Remove sensitive fields
|
// Remove sensitive fields
|
||||||
post.deleteSensitiveFields()
|
post.deleteSensitiveFields();
|
||||||
|
|
||||||
res.send(post);
|
res.send(post);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /posts/followed:
|
* /posts/followed:
|
||||||
* get:
|
* get:
|
||||||
* security:
|
* security:
|
||||||
* - bearerAuth: []
|
* - bearerAuth: []
|
||||||
* tags:
|
* tags:
|
||||||
* - Posts
|
* - Posts
|
||||||
* operationId: getFollowedPosts
|
* operationId: getFollowedPosts
|
||||||
* summary: Get posts of followed users
|
* summary: Get posts of followed users
|
||||||
* responses:
|
* responses:
|
||||||
* 200:
|
* 200:
|
||||||
* description: A list of posts
|
* description: A list of posts
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
* type: array
|
* type: array
|
||||||
* items:
|
* items:
|
||||||
* $ref: '#/components/schemas/Post'
|
* $ref: '#/components/schemas/Post'
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public getFollowedPosts = catchAsync(async (req : AppRequest, res, _next) => {
|
public getFollowedPosts = catchAsync(async (req: AppRequest, res, _next) => {
|
||||||
const user = await this.userRepository.findOne({
|
const user = await this.userRepository.findOne({
|
||||||
where: { id: req.user.id },
|
where: { id: req.user.id },
|
||||||
relations: {
|
relations: {
|
||||||
followed: true,
|
followed: true,
|
||||||
posts: { likedBy: true, comments: true },
|
posts: { likedBy: true, comments: true },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const followedPosts = user.followed.map(followedUser => followedUser.posts).flat()
|
const followedPosts = user.followed
|
||||||
|
.map((followedUser) => followedUser.posts)
|
||||||
|
.flat();
|
||||||
|
|
||||||
// Remove sensitive fields
|
// Remove sensitive fields
|
||||||
followedPosts.forEach(post => {
|
followedPosts.forEach((post) => {
|
||||||
post.deleteSensitiveFields()
|
post.deleteSensitiveFields();
|
||||||
})
|
});
|
||||||
|
|
||||||
res.send(followedPosts);
|
res.send(followedPosts);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /posts:
|
* /posts:
|
||||||
* post:
|
* post:
|
||||||
* security:
|
* security:
|
||||||
* - bearerAuth: []
|
* - bearerAuth: []
|
||||||
* tags:
|
* tags:
|
||||||
* - Posts
|
* - Posts
|
||||||
* operationId: createPost
|
* operationId: createPost
|
||||||
* summary: Create a post
|
* summary: Create a post
|
||||||
* requestBody:
|
* requestBody:
|
||||||
* required: true
|
* required: true
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
* type: object
|
* type: object
|
||||||
* properties:
|
* properties:
|
||||||
* title:
|
* title:
|
||||||
* type: string
|
* type: string
|
||||||
* description: Post title
|
* description: Post title
|
||||||
* content:
|
* content:
|
||||||
* type: string
|
* type: string
|
||||||
* description: Post content
|
* description: Post content
|
||||||
* responses:
|
* responses:
|
||||||
* 201:
|
* 201:
|
||||||
* description: A post
|
* description: A post
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
* $ref: '#/components/schemas/Post'
|
* $ref: '#/components/schemas/Post'
|
||||||
* 400:
|
* 400:
|
||||||
* description: Title and content are required
|
* description: Title and content are required
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public createPost = catchAsync(async (req : AppRequest, res, next) => {
|
public createPost = catchAsync(async (req: AppRequest, res, next) => {
|
||||||
const user = await this.userRepository.findOne({where: {id: req.user.id}})
|
const user = await this.userRepository.findOne({
|
||||||
const {title, content} = req.body
|
where: { id: req.user.id },
|
||||||
|
});
|
||||||
|
const { title, content } = req.body;
|
||||||
|
|
||||||
if(!title || !content) return next(new AppError('Title and content are required', 400))
|
if (!title || !content)
|
||||||
|
return next(new AppError("Title and content are required", 400));
|
||||||
|
|
||||||
|
const newPost = Object.assign(new Post(), {
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
createdBy: user,
|
||||||
|
createdAt: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
const newPost = Object.assign(new Post(), {
|
const post = await this.postRepository.save(newPost);
|
||||||
title,
|
|
||||||
content,
|
|
||||||
createdBy: user,
|
|
||||||
createdAt: new Date()})
|
|
||||||
|
|
||||||
const post = await this.postRepository.save(newPost)
|
// Remove sensitive fields
|
||||||
|
post.deleteSensitiveFields();
|
||||||
// Remove sensitive fields
|
|
||||||
post.deleteSensitiveFields()
|
|
||||||
|
|
||||||
res.status(201).send(post);
|
res.status(201).send(post);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /posts/{id}:
|
* /posts/{id}:
|
||||||
* patch:
|
* patch:
|
||||||
* security:
|
* security:
|
||||||
* - bearerAuth: []
|
* - bearerAuth: []
|
||||||
* tags:
|
* tags:
|
||||||
* - Posts
|
* - Posts
|
||||||
* operationId: updatePost
|
* operationId: updatePost
|
||||||
* summary: Update a post by ID
|
* summary: Update a post by ID
|
||||||
* parameters:
|
* parameters:
|
||||||
* - in: path
|
* - in: path
|
||||||
* name: id
|
* name: id
|
||||||
* required: true
|
* required: true
|
||||||
* description: ID of the post
|
* description: ID of the post
|
||||||
* schema:
|
* schema:
|
||||||
* type: integer
|
* type: integer
|
||||||
* requestBody:
|
* requestBody:
|
||||||
* required: true
|
* required: true
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
* type: object
|
* type: object
|
||||||
* properties:
|
* properties:
|
||||||
* title:
|
* title:
|
||||||
* type: string
|
* type: string
|
||||||
* description: Post title
|
* description: Post title
|
||||||
* content:
|
* content:
|
||||||
* type: string
|
* type: string
|
||||||
* description: Post content
|
* description: Post content
|
||||||
* responses:
|
* responses:
|
||||||
* 200:
|
* 200:
|
||||||
* description: A post
|
* description: A post
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
* $ref: '#/components/schemas/Post'
|
* $ref: '#/components/schemas/Post'
|
||||||
* 400:
|
* 400:
|
||||||
* description: Invalid ID or Title and content are required
|
* description: Invalid ID or Title and content are required
|
||||||
* 404:
|
* 404:
|
||||||
* description: No post found with that ID
|
* description: No post found with that ID
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public updatePost = catchAsync(async (req : AppRequest, res, next) => {
|
public updatePost = catchAsync(async (req: AppRequest, res, next) => {
|
||||||
const {user, post, errorMessage} = await this.validateRequestAndGetEntities(req)
|
const { user, post, errorMessage } =
|
||||||
const {title, content} = req.body
|
await this.validateRequestAndGetEntities(req);
|
||||||
|
const { title, content } = req.body;
|
||||||
|
|
||||||
if(!title || !content) return next(new AppError('Title and content are required', 400))
|
if (!title || !content)
|
||||||
|
return next(new AppError("Title and content are required", 400));
|
||||||
|
|
||||||
if(errorMessage == 'Invalid ID') return next(new AppError('Invalid ID', 400))
|
if (errorMessage == "Invalid ID")
|
||||||
if(errorMessage == 'No post found with that ID'){
|
return next(new AppError("Invalid ID", 400));
|
||||||
return next(new AppError('No post found with that ID', 404))
|
if (errorMessage == "No post found with that ID") {
|
||||||
}
|
return next(new AppError("No post found with that ID", 404));
|
||||||
|
}
|
||||||
|
|
||||||
if(post.createdBy.id !== user.id){
|
if (post.createdBy.id !== user.id) {
|
||||||
return next(new AppError('You are not authorized to update this post', 403))
|
return next(
|
||||||
}
|
new AppError("You are not authorized to update this post", 403)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
post.title = title
|
post.title = title;
|
||||||
post.content = content
|
post.content = content;
|
||||||
await this.postRepository.save(post)
|
await this.postRepository.save(post);
|
||||||
|
|
||||||
// Remove sensitive fields
|
// Remove sensitive fields
|
||||||
post.deleteSensitiveFields()
|
post.deleteSensitiveFields();
|
||||||
|
|
||||||
res.status(200).send(post);
|
res.status(200).send(post);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /posts/{id}:
|
* /posts/{id}:
|
||||||
* delete:
|
* delete:
|
||||||
* security:
|
* security:
|
||||||
* - bearerAuth: []
|
* - bearerAuth: []
|
||||||
* tags:
|
* tags:
|
||||||
* - Posts
|
* - Posts
|
||||||
* operationId: deletePost
|
* operationId: deletePost
|
||||||
* summary: Delete a post by ID
|
* summary: Delete a post by ID
|
||||||
* parameters:
|
* parameters:
|
||||||
* - in: path
|
* - in: path
|
||||||
* name: id
|
* name: id
|
||||||
* required: true
|
* required: true
|
||||||
* description: ID of the post
|
* description: ID of the post
|
||||||
* schema:
|
* schema:
|
||||||
* type: integer
|
* type: integer
|
||||||
* responses:
|
* responses:
|
||||||
* 204:
|
* 204:
|
||||||
* description: No content
|
* description: No content
|
||||||
* 400:
|
* 400:
|
||||||
* description: Invalid ID
|
* description: Invalid ID
|
||||||
* 404:
|
* 404:
|
||||||
* description: No post found with that ID
|
* description: No post found with that ID
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public deletePost = catchAsync(async (req : AppRequest, res, next) => {
|
public deletePost = catchAsync(async (req: AppRequest, res, next) => {
|
||||||
const {user, post, errorMessage} = await this.validateRequestAndGetEntities(req)
|
const { user, post, errorMessage } =
|
||||||
|
await this.validateRequestAndGetEntities(req);
|
||||||
|
|
||||||
if(errorMessage == 'Invalid ID') return next(new AppError('Invalid ID', 400))
|
if (errorMessage == "Invalid ID")
|
||||||
if(errorMessage == 'No post found with that ID') return next(new AppError('No post found with that ID', 404))
|
return next(new AppError("Invalid ID", 400));
|
||||||
|
if (errorMessage == "No post found with that ID")
|
||||||
|
return next(new AppError("No post found with that ID", 404));
|
||||||
|
|
||||||
if(post.createdBy.id !== user.id){
|
if (post.createdBy.id !== user.id) {
|
||||||
return next(new AppError('You are not authorized to delete this post', 403))
|
return next(
|
||||||
}
|
new AppError("You are not authorized to delete this post", 403)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await this.postRepository.remove(post);
|
await this.postRepository.remove(post);
|
||||||
res.status(204).send();
|
res.status(204).send();
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /posts/{id}/like:
|
* /posts/{id}/like:
|
||||||
* post:
|
* post:
|
||||||
* security:
|
* security:
|
||||||
* - bearerAuth: []
|
* - bearerAuth: []
|
||||||
* tags:
|
* tags:
|
||||||
* - Posts
|
* - Posts
|
||||||
* operationId: likePost
|
* operationId: likePost
|
||||||
* summary: Like a post by ID
|
* summary: Like a post by ID
|
||||||
* parameters:
|
* parameters:
|
||||||
* - in: path
|
* - in: path
|
||||||
* name: id
|
* name: id
|
||||||
* required: true
|
* required: true
|
||||||
* description: ID of the post
|
* description: ID of the post
|
||||||
* schema:
|
* schema:
|
||||||
* type: integer
|
* type: integer
|
||||||
* responses:
|
* responses:
|
||||||
* 200:
|
* 200:
|
||||||
* description: A post
|
* description: A post
|
||||||
* content:
|
* content:
|
||||||
* application/json:
|
* application/json:
|
||||||
* schema:
|
* schema:
|
||||||
* $ref: '#/components/schemas/Post'
|
* $ref: '#/components/schemas/Post'
|
||||||
* 400:
|
* 400:
|
||||||
* description: Invalid ID or You have already liked this post
|
* description: Invalid ID or You have already liked this post
|
||||||
* 404:
|
* 404:
|
||||||
* description: No post found with that ID
|
* description: No post found with that ID
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public likePost = catchAsync(async (req : AppRequest, res, next) => {
|
public likePost = catchAsync(async (req: AppRequest, res, next) => {
|
||||||
const {user, post, errorMessage} = await this.validateRequestAndGetEntities(req)
|
const { user, post, errorMessage } =
|
||||||
|
await this.validateRequestAndGetEntities(req);
|
||||||
if(errorMessage == 'Invalid ID') return next(new AppError('Invalid ID', 400))
|
|
||||||
if(errorMessage == 'No post found with that ID'){
|
|
||||||
return next(new AppError('No post found with that ID', 404))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if user has already liked the post
|
|
||||||
if(post.likedBy.some(likedUser => likedUser.id === user.id)){
|
|
||||||
return next(new AppError('You have already liked this post', 400))
|
|
||||||
}
|
|
||||||
|
|
||||||
post.likedBy.push(user)
|
|
||||||
await this.postRepository.save(post)
|
|
||||||
|
|
||||||
// Send notification to post creator
|
|
||||||
const newNotification = Object.assign(new Notification(),{
|
|
||||||
message: `${user.firstName} ${user.lastName} liked your post`,
|
|
||||||
belongsTo: post.createdBy,
|
|
||||||
timeStamp: new Date()
|
|
||||||
})
|
|
||||||
const notification = await this.notificationRepository.save(newNotification)
|
|
||||||
post.createdBy.notifications.push(notification)
|
|
||||||
await this.userRepository.save(post.createdBy)
|
|
||||||
|
|
||||||
// Remove sensitive fields
|
|
||||||
post.deleteSensitiveFields()
|
|
||||||
|
|
||||||
res.status(200).send(post);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /posts/{id}/like:
|
|
||||||
* delete:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags:
|
|
||||||
* - Posts
|
|
||||||
* operationId: unlikePost
|
|
||||||
* summary: Unlike a post by ID
|
|
||||||
* parameters:
|
|
||||||
* - in: path
|
|
||||||
* name: id
|
|
||||||
* required: true
|
|
||||||
* description: ID of the post
|
|
||||||
* schema:
|
|
||||||
* type: integer
|
|
||||||
* responses:
|
|
||||||
* 200:
|
|
||||||
* description: A post
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* $ref: '#/components/schemas/Post'
|
|
||||||
* 400:
|
|
||||||
* description: Invalid ID or You have not liked this post
|
|
||||||
* 404:
|
|
||||||
* description: No post found with that ID
|
|
||||||
*/
|
|
||||||
|
|
||||||
public unlikePost = catchAsync(async (req : AppRequest, res, next) => {
|
|
||||||
const {user, post, errorMessage} = await this.validateRequestAndGetEntities(req)
|
|
||||||
|
|
||||||
if(errorMessage == 'Invalid ID') return next(new AppError('Invalid ID', 400))
|
|
||||||
if(errorMessage == 'No post found with that ID') return next(new AppError('No post found with that ID', 404))
|
|
||||||
|
|
||||||
// Check if user likes the post
|
|
||||||
if(!post.likedBy.some(likedUser => likedUser.id === user.id)){
|
|
||||||
return next(new AppError('You have not liked this post', 400))
|
|
||||||
}
|
|
||||||
|
|
||||||
post.likedBy = post.likedBy.filter(likedUser => likedUser.id !== user.id)
|
|
||||||
await this.postRepository.save(post)
|
|
||||||
|
|
||||||
// Remove sensitive fields
|
|
||||||
post.deleteSensitiveFields()
|
|
||||||
|
|
||||||
res.status(200).send(post);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @swagger
|
|
||||||
* /posts/{id}/comment:
|
|
||||||
* post:
|
|
||||||
* security:
|
|
||||||
* - bearerAuth: []
|
|
||||||
* tags:
|
|
||||||
* - Posts
|
|
||||||
* operationId: commentPost
|
|
||||||
* summary: Comment on a post by ID
|
|
||||||
* parameters:
|
|
||||||
* - in: path
|
|
||||||
* name: id
|
|
||||||
* required: true
|
|
||||||
* description: ID of the post
|
|
||||||
* schema:
|
|
||||||
* type: integer
|
|
||||||
* requestBody:
|
|
||||||
* required: true
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* type: object
|
|
||||||
* properties:
|
|
||||||
* content:
|
|
||||||
* type: string
|
|
||||||
* description: Comment content
|
|
||||||
* responses:
|
|
||||||
* 201:
|
|
||||||
* description: A comment
|
|
||||||
* content:
|
|
||||||
* application/json:
|
|
||||||
* schema:
|
|
||||||
* $ref: '#/components/schemas/Comment'
|
|
||||||
* 400:
|
|
||||||
* description: Invalid Request
|
|
||||||
* 404:
|
|
||||||
* description: No post found with that ID
|
|
||||||
*/
|
|
||||||
|
|
||||||
public commentPost = catchAsync(async (req : AppRequest, res, next) => {
|
|
||||||
const {user, post, errorMessage} = await this.validateRequestAndGetEntities(req)
|
|
||||||
|
|
||||||
if(errorMessage == 'Invalid ID') return next(new AppError('Invalid ID', 400))
|
|
||||||
if(errorMessage == 'No post found with that ID') {
|
|
||||||
return next(new AppError('No post found with that ID', 404))
|
|
||||||
}
|
|
||||||
|
|
||||||
const {content} = req.body
|
|
||||||
const newComment = Object.assign(new Comment(), {
|
|
||||||
content,
|
|
||||||
createdBy: user,
|
|
||||||
post,
|
|
||||||
createdAt: new Date()
|
|
||||||
})
|
|
||||||
|
|
||||||
const comment = await this.commentRepository.save(newComment)
|
|
||||||
|
|
||||||
// Send notification to post creator
|
|
||||||
const newNotification = Object.assign(new Notification(),{
|
|
||||||
message: `${user.firstName} ${user.lastName} commented on your post`,
|
|
||||||
belongsTo: post.createdBy,
|
|
||||||
timeStamp: new Date()
|
|
||||||
})
|
|
||||||
await this.notificationRepository.save(newNotification)
|
|
||||||
|
|
||||||
// Remove sensitive fields
|
|
||||||
comment.createdBy.deleteSensitiveFields()
|
|
||||||
post.deleteSensitiveFields()
|
|
||||||
|
|
||||||
res.status(201).send(comment)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
private validateRequestAndGetEntities = async (req : AppRequest,) : Promise<validatedEntities> => {
|
|
||||||
const postId = req.params.id
|
|
||||||
const parsedId = parseInt(postId)
|
|
||||||
let errorMessage: 'Invalid ID' | 'No post found with that ID'
|
|
||||||
|
|
||||||
// Check if ID is a number
|
|
||||||
if(isNaN(parsedId)) errorMessage = 'Invalid ID'
|
|
||||||
|
|
||||||
const user = req.user
|
|
||||||
const post = await this.postRepository.findOne({
|
|
||||||
where: {id: parsedId},
|
|
||||||
relations: {
|
|
||||||
likedBy: true,
|
|
||||||
comments: { createdBy: true },
|
|
||||||
createdBy: {notifications: true}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Check if post exists
|
|
||||||
if(!post) errorMessage = 'No post found with that ID'
|
|
||||||
|
|
||||||
return {user, post, errorMessage}
|
|
||||||
|
|
||||||
|
if (errorMessage == "Invalid ID")
|
||||||
|
return next(new AppError("Invalid ID", 400));
|
||||||
|
if (errorMessage == "No post found with that ID") {
|
||||||
|
return next(new AppError("No post found with that ID", 404));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if user has already liked the post
|
||||||
|
if (post.likedBy.some((likedUser) => likedUser.id === user.id)) {
|
||||||
|
return next(new AppError("You have already liked this post", 400));
|
||||||
|
}
|
||||||
|
|
||||||
|
post.likedBy.push(user);
|
||||||
|
await this.postRepository.save(post);
|
||||||
|
|
||||||
|
// Send notification to post creator
|
||||||
|
const newNotification = Object.assign(new Notification(), {
|
||||||
|
message: `${user.firstName} ${user.lastName} liked your post`,
|
||||||
|
belongsTo: post.createdBy,
|
||||||
|
timeStamp: new Date(),
|
||||||
|
});
|
||||||
|
const notification = await this.notificationRepository.save(
|
||||||
|
newNotification
|
||||||
|
);
|
||||||
|
post.createdBy.notifications.push(notification);
|
||||||
|
await this.userRepository.save(post.createdBy);
|
||||||
|
|
||||||
|
// Remove sensitive fields
|
||||||
|
post.deleteSensitiveFields();
|
||||||
|
|
||||||
|
res.status(200).send(post);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /posts/{id}/like:
|
||||||
|
* delete:
|
||||||
|
* security:
|
||||||
|
* - bearerAuth: []
|
||||||
|
* tags:
|
||||||
|
* - Posts
|
||||||
|
* operationId: unlikePost
|
||||||
|
* summary: Unlike a post by ID
|
||||||
|
* parameters:
|
||||||
|
* - in: path
|
||||||
|
* name: id
|
||||||
|
* required: true
|
||||||
|
* description: ID of the post
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: A post
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Post'
|
||||||
|
* 400:
|
||||||
|
* description: Invalid ID or You have not liked this post
|
||||||
|
* 404:
|
||||||
|
* description: No post found with that ID
|
||||||
|
*/
|
||||||
|
|
||||||
|
public unlikePost = catchAsync(async (req: AppRequest, res, next) => {
|
||||||
|
const { user, post, errorMessage } =
|
||||||
|
await this.validateRequestAndGetEntities(req);
|
||||||
|
|
||||||
|
if (errorMessage == "Invalid ID")
|
||||||
|
return next(new AppError("Invalid ID", 400));
|
||||||
|
if (errorMessage == "No post found with that ID")
|
||||||
|
return next(new AppError("No post found with that ID", 404));
|
||||||
|
|
||||||
|
// Check if user likes the post
|
||||||
|
if (!post.likedBy.some((likedUser) => likedUser.id === user.id)) {
|
||||||
|
return next(new AppError("You have not liked this post", 400));
|
||||||
|
}
|
||||||
|
|
||||||
|
post.likedBy = post.likedBy.filter((likedUser) => likedUser.id !== user.id);
|
||||||
|
await this.postRepository.save(post);
|
||||||
|
|
||||||
|
// Remove sensitive fields
|
||||||
|
post.deleteSensitiveFields();
|
||||||
|
|
||||||
|
res.status(200).send(post);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /posts/{id}/comment:
|
||||||
|
* post:
|
||||||
|
* security:
|
||||||
|
* - bearerAuth: []
|
||||||
|
* tags:
|
||||||
|
* - Posts
|
||||||
|
* operationId: commentPost
|
||||||
|
* summary: Comment on a post by ID
|
||||||
|
* parameters:
|
||||||
|
* - in: path
|
||||||
|
* name: id
|
||||||
|
* required: true
|
||||||
|
* description: ID of the post
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* requestBody:
|
||||||
|
* required: true
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* content:
|
||||||
|
* type: string
|
||||||
|
* description: Comment content
|
||||||
|
* responses:
|
||||||
|
* 201:
|
||||||
|
* description: A comment
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: '#/components/schemas/Comment'
|
||||||
|
* 400:
|
||||||
|
* description: Invalid Request
|
||||||
|
* 404:
|
||||||
|
* description: No post found with that ID
|
||||||
|
*/
|
||||||
|
|
||||||
|
public commentPost = catchAsync(async (req: AppRequest, res, next) => {
|
||||||
|
const { user, post, errorMessage } =
|
||||||
|
await this.validateRequestAndGetEntities(req);
|
||||||
|
|
||||||
|
if (errorMessage == "Invalid ID")
|
||||||
|
return next(new AppError("Invalid ID", 400));
|
||||||
|
if (errorMessage == "No post found with that ID") {
|
||||||
|
return next(new AppError("No post found with that ID", 404));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { content } = req.body;
|
||||||
|
const newComment = Object.assign(new Comment(), {
|
||||||
|
content,
|
||||||
|
createdBy: user,
|
||||||
|
post,
|
||||||
|
createdAt: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const comment = await this.commentRepository.save(newComment);
|
||||||
|
|
||||||
|
// Send notification to post creator
|
||||||
|
const newNotification = Object.assign(new Notification(), {
|
||||||
|
message: `${user.firstName} ${user.lastName} commented on your post`,
|
||||||
|
belongsTo: post.createdBy,
|
||||||
|
timeStamp: new Date(),
|
||||||
|
});
|
||||||
|
await this.notificationRepository.save(newNotification);
|
||||||
|
|
||||||
|
// Remove sensitive fields
|
||||||
|
comment.createdBy.deleteSensitiveFields();
|
||||||
|
post.deleteSensitiveFields();
|
||||||
|
|
||||||
|
res.status(201).send(comment);
|
||||||
|
});
|
||||||
|
|
||||||
|
private validateRequestAndGetEntities = async (
|
||||||
|
req: AppRequest
|
||||||
|
): Promise<validatedEntities> => {
|
||||||
|
const postId = req.params.id;
|
||||||
|
const parsedId = parseInt(postId);
|
||||||
|
let errorMessage: "Invalid ID" | "No post found with that ID";
|
||||||
|
|
||||||
|
// Check if ID is a number
|
||||||
|
if (isNaN(parsedId)) errorMessage = "Invalid ID";
|
||||||
|
|
||||||
|
const user = req.user;
|
||||||
|
const post = await this.postRepository.findOne({
|
||||||
|
where: { id: parsedId },
|
||||||
|
relations: {
|
||||||
|
likedBy: true,
|
||||||
|
comments: { createdBy: true },
|
||||||
|
createdBy: { notifications: true },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if post exists
|
||||||
|
if (!post) errorMessage = "No post found with that ID";
|
||||||
|
|
||||||
|
return { user, post, errorMessage };
|
||||||
|
};
|
||||||
}
|
}
|
||||||
interface validatedEntities {
|
interface validatedEntities {
|
||||||
user: User
|
user: User
|
||||||
|
|
|
||||||
|
|
@ -53,10 +53,16 @@ export class User {
|
||||||
}
|
}
|
||||||
|
|
||||||
public deleteSensitiveFields() {
|
public deleteSensitiveFields() {
|
||||||
delete this.password;
|
this.deleteExtraFields();
|
||||||
delete this.email;
|
this.deletePrivateFields();
|
||||||
delete this.notifications;
|
}
|
||||||
|
public deleteExtraFields() {
|
||||||
delete this.followed;
|
delete this.followed;
|
||||||
delete this.followers;
|
delete this.followers;
|
||||||
}
|
}
|
||||||
|
public deletePrivateFields(){
|
||||||
|
delete this.email;
|
||||||
|
delete this.password;
|
||||||
|
delete this.notifications;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue