🗃️ Private user setup, and preparations for images upload
Signed-off-by: Pau Costa <mico@micodev.es>main
parent
be57bd32b8
commit
fda05c661a
|
|
@ -108,6 +108,14 @@
|
||||||
"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"
|
||||||
|
|
@ -769,9 +777,7 @@
|
||||||
},
|
},
|
||||||
"/users/me": {
|
"/users/me": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": ["Users"],
|
||||||
"Users"
|
|
||||||
],
|
|
||||||
"summary": "Get the currently logged in user",
|
"summary": "Get the currently logged in user",
|
||||||
"security": [
|
"security": [
|
||||||
{
|
{
|
||||||
|
|
@ -790,6 +796,50 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"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": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
.gitignore
|
.gitignore
|
||||||
.npmignore
|
.npmignore
|
||||||
.openapi-generator-ignore
|
|
||||||
api.ts
|
api.ts
|
||||||
base.ts
|
base.ts
|
||||||
common.ts
|
common.ts
|
||||||
|
|
|
||||||
|
|
@ -284,6 +284,18 @@ 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}
|
||||||
|
|
@ -309,6 +321,18 @@ 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}
|
||||||
|
|
@ -358,6 +382,18 @@ 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}
|
||||||
|
|
@ -395,6 +431,25 @@ 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
|
||||||
|
|
@ -1528,6 +1583,46 @@ 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,
|
||||||
|
|
@ -1606,6 +1701,19 @@ 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);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1664,6 +1772,16 @@ 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));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1731,6 +1849,18 @@ 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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,10 @@ export class UserController {
|
||||||
user.deleteSensitiveFields();
|
user.deleteSensitiveFields();
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(200).send(users);
|
// remove the users set to private
|
||||||
|
const publicUsers = users.filter((user) => user.isPrivate === false);
|
||||||
|
|
||||||
|
res.status(200).send(publicUsers);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -67,7 +70,7 @@ export class UserController {
|
||||||
* $ref: '#/components/schemas/UserWithRelations'
|
* $ref: '#/components/schemas/UserWithRelations'
|
||||||
*/
|
*/
|
||||||
public getUser = catchAsync(
|
public getUser = catchAsync(
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: AppRequest, 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
|
||||||
|
|
@ -92,6 +95,18 @@ 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);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
@ -135,6 +150,60 @@ 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,53 +6,57 @@ import {Notification} from "./Notification";
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class User {
|
export class User {
|
||||||
|
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id: number
|
id: number;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
firstName: string
|
firstName: string;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
lastName: string
|
lastName: string;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
email: string
|
email: string;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
password: string
|
password: string;
|
||||||
|
|
||||||
@ManyToMany(type => User)
|
@Column({ default: true })
|
||||||
|
isPrivate: boolean;
|
||||||
|
|
||||||
|
@Column({ default: null })
|
||||||
|
profilePictureId: string;
|
||||||
|
|
||||||
|
@ManyToMany((type) => User)
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
followed: User[]
|
followed: User[];
|
||||||
|
|
||||||
@ManyToMany(type => User)
|
@ManyToMany((type) => User)
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
followers: User[]
|
followers: User[];
|
||||||
|
|
||||||
@OneToMany(type => Post, post=> post.createdBy)
|
@OneToMany((type) => Post, (post) => post.createdBy)
|
||||||
posts: Post[]
|
posts: Post[];
|
||||||
|
|
||||||
@OneToMany(type => Comment, comment=> comment.createdBy)
|
@OneToMany((type) => Comment, (comment) => comment.createdBy)
|
||||||
comments: Comment[]
|
comments: Comment[];
|
||||||
|
|
||||||
@OneToMany(type => Notification, notification => notification.belongsTo)
|
@OneToMany((type) => Notification, (notification) => notification.belongsTo)
|
||||||
notifications: Notification[]
|
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,6 +115,14 @@ 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,7 +6,9 @@ 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").get(userController.getMe)
|
UserRoutes.route("/me")
|
||||||
|
.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