Skip to content

Commit

Permalink
Newsfeed management Updated and required changes (#1383)
Browse files Browse the repository at this point in the history
* implementation of the newsfeed feature

* implementation of the newsfeed feature
  • Loading branch information
aashimawadhwa authored Sep 20, 2023
1 parent 810457b commit 3973d69
Show file tree
Hide file tree
Showing 26 changed files with 414 additions and 380 deletions.
5 changes: 3 additions & 2 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,7 @@ type Post {
likeCount: Int
likedBy: [User]
organization: Organization!
pinned: Boolean
text: String!
title: String
videoUrl: URL
Expand Down Expand Up @@ -669,10 +670,10 @@ enum PostOrderByInput {
}

input PostUpdateInput {
imageUrl: URL
imageUrl: String
text: String
title: String
videoUrl: URL
videoUrl: String
}

input PostWhereInput {
Expand Down
2 changes: 2 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ app.use(
);

app.use("/images", express.static(path.join(__dirname, "./../images")));
app.use("/videos", express.static(path.join(__dirname, "./../videos")));

app.use(requestContext.middleware());

if (process.env.NODE_ENV !== "production")
Expand Down
39 changes: 39 additions & 0 deletions src/models/EncodedVideo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { Types, Model } from "mongoose";
import { Schema, model, models } from "mongoose";
/**
* This is an interface that represents a database(MongoDB) document for Encoded Video.
*/
export interface InterfaceEncodedVideo {
_id: Types.ObjectId;
fileName: string;
content: string;
numberOfUses: number;
}
/**
* This describes the schema for a `encodedVideo` that corresponds to `InterfaceEncodedVideo` document.
* @param fileName - File name.
* @param content - Content.
* @param numberOfUses - Number of Uses.
*/
const encodedVideoSchema = new Schema({
fileName: {
type: String,
required: true,
},
content: {
type: String,
required: true,
},
numberOfUses: {
type: Number,
required: true,
default: 1,
},
});

const encodedVideoModel = (): Model<InterfaceEncodedVideo> =>
model<InterfaceEncodedVideo>("EncodedVideo", encodedVideoSchema);

// This syntax is needed to prevent Mongoose OverwriteModelError while running tests.
export const EncodedVideo = (models.EncodedVideo ||
encodedVideoModel()) as ReturnType<typeof encodedVideoModel>;
2 changes: 1 addition & 1 deletion src/models/Post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface InterfacePost {
status: string;
createdAt: Date;
imageUrl: string | undefined | null;
videoUrl: string | undefined;
videoUrl: string | undefined | null;
creator: PopulatedDoc<InterfaceUser & Document>;
organization: PopulatedDoc<InterfaceOrganization & Document>;
likedBy: PopulatedDoc<InterfaceUser & Document>[];
Expand Down
22 changes: 18 additions & 4 deletions src/resolvers/Mutation/createPost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import {
} from "../../constants";
import { isValidString } from "../../libraries/validators/validateString";
import { uploadEncodedImage } from "../../utilities/encodedImageStorage/uploadEncodedImage";
import { uploadEncodedVideo } from "../../utilities/encodedVideoStorage/uploadEncodedVideo";
import { findOrganizationsInCache } from "../../services/OrganizationCache/findOrganizationsInCache";
import { cacheOrganizations } from "../../services/OrganizationCache/cacheOrganizations";

/**
* This function enables to create a post.
* @param _parent - parent of current request
Expand Down Expand Up @@ -65,10 +67,18 @@ export const createPost: MutationResolvers["createPost"] = async (
);
}

let uploadImageFileName;
let uploadImageFileName = null;
let uploadVideoFileName = null;

if (args.file) {
uploadImageFileName = await uploadEncodedImage(args.file, null);
const dataUrlPrefix = "data:";
if (args.file.startsWith(dataUrlPrefix + "image/")) {
uploadImageFileName = await uploadEncodedImage(args.file, null);
} else if (args.file.startsWith(dataUrlPrefix + "video/")) {
uploadVideoFileName = await uploadEncodedVideo(args.file, null);
} else {
throw new Error("Unsupported file type.");
}
}

// Checks if the recieved arguments are valid according to standard input norms
Expand Down Expand Up @@ -118,7 +128,8 @@ export const createPost: MutationResolvers["createPost"] = async (
pinned: args.data.pinned ? true : false,
creator: context.userId,
organization: args.data.organizationId,
imageUrl: args.file ? uploadImageFileName : null,
imageUrl: uploadImageFileName,
videoUrl: uploadVideoFileName,
});

if (args.data.pinned) {
Expand All @@ -141,8 +152,11 @@ export const createPost: MutationResolvers["createPost"] = async (
// Returns createdPost.
return {
...createdPost.toObject(),
imageUrl: createdPost.imageUrl
imageUrl: uploadImageFileName
? `${context.apiRootUrl}${uploadImageFileName}`
: null,
videoUrl: uploadVideoFileName
? `${context.apiRootUrl}${uploadVideoFileName}`
: null,
};
};
16 changes: 16 additions & 0 deletions src/resolvers/Mutation/updatePost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
LENGTH_VALIDATION_ERROR,
} from "../../constants";
import { isValidString } from "../../libraries/validators/validateString";
import { uploadEncodedImage } from "../../utilities/encodedImageStorage/uploadEncodedImage";
import { uploadEncodedVideo } from "../../utilities/encodedVideoStorage/uploadEncodedVideo";

export const updatePost: MutationResolvers["updatePost"] = async (
_parent,
Expand Down Expand Up @@ -51,6 +53,20 @@ export const updatePost: MutationResolvers["updatePost"] = async (
);
}

if (args.data?.imageUrl && args.data?.imageUrl !== null) {
args.data.imageUrl = await uploadEncodedImage(
args.data.imageUrl,
post.imageUrl
);
}

if (args.data?.videoUrl && args.data?.videoUrl !== null) {
args.data.videoUrl = await uploadEncodedVideo(
args.data.videoUrl,
post.videoUrl
);
}

// Checks if the recieved arguments are valid according to standard input norms
const validationResultTitle = isValidString(args.data?.title ?? "", 256);
const validationResultText = isValidString(args.data?.text ?? "", 500);
Expand Down
1 change: 1 addition & 0 deletions src/resolvers/Query/postsByOrganization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const postsByOrganization: QueryResolvers["postsByOrganization"] =
const postsWithImageURLResolved = postsInOrg.map((post) => ({
...post,
imageUrl: post.imageUrl ? `${context.apiRootUrl}${post.imageUrl}` : null,
videoUrl: post.videoUrl ? `${context.apiRootUrl}${post.videoUrl}` : null,
}));

return postsWithImageURLResolved;
Expand Down
3 changes: 3 additions & 0 deletions src/resolvers/Query/postsByOrganizationConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ export const postsByOrganizationConnection: QueryResolvers["postsByOrganizationC
? `${context.apiRootUrl}${post.imageUrl}`
: null;

post.videoUrl = post.videoUrl
? `${context.apiRootUrl}${post.videoUrl}`
: null;
return post;
});

Expand Down
4 changes: 2 additions & 2 deletions src/typeDefs/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ export const inputs = gql`
input PostUpdateInput {
text: String
title: String
imageUrl: URL
videoUrl: URL
imageUrl: String
videoUrl: String
}
`;
1 change: 1 addition & 0 deletions src/typeDefs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ export const types = gql`
comments: [Comment]
likeCount: Int
commentCount: Int
pinned: Boolean
}
"""
Expand Down
6 changes: 4 additions & 2 deletions src/types/generatedGraphQLTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,7 @@ export type Post = {
likeCount?: Maybe<Scalars['Int']>;
likedBy?: Maybe<Array<Maybe<User>>>;
organization: Organization;
pinned?: Maybe<Scalars['Boolean']>;
text: Scalars['String'];
title?: Maybe<Scalars['String']>;
videoUrl?: Maybe<Scalars['URL']>;
Expand Down Expand Up @@ -1174,10 +1175,10 @@ export type PostOrderByInput =
| 'videoUrl_DESC';

export type PostUpdateInput = {
imageUrl?: InputMaybe<Scalars['URL']>;
imageUrl?: InputMaybe<Scalars['String']>;
text?: InputMaybe<Scalars['String']>;
title?: InputMaybe<Scalars['String']>;
videoUrl?: InputMaybe<Scalars['URL']>;
videoUrl?: InputMaybe<Scalars['String']>;
};

export type PostWhereInput = {
Expand Down Expand Up @@ -2473,6 +2474,7 @@ export type PostResolvers<ContextType = any, ParentType extends ResolversParentT
likeCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
likedBy?: Resolver<Maybe<Array<Maybe<ResolversTypes['User']>>>, ParentType, ContextType>;
organization?: Resolver<ResolversTypes['Organization'], ParentType, ContextType>;
pinned?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
text?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
title?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
videoUrl?: Resolver<Maybe<ResolversTypes['URL']>, ParentType, ContextType>;
Expand Down
29 changes: 29 additions & 0 deletions src/utilities/encodedVideoStorage/deletePreviousVideo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { unlink } from "fs/promises";
import path from "path";
import { EncodedVideo } from "../../models/EncodedVideo";

export const deletePreviousVideo = async (
videoToBeDeletedPath: string
): Promise<void> => {
const videoToBeDeleted = await EncodedVideo.findOne({
fileName: videoToBeDeletedPath!,
});

if (videoToBeDeleted?.numberOfUses === 1) {
await unlink(path.join(__dirname, "../../../" + videoToBeDeleted.fileName));
await EncodedVideo.deleteOne({
fileName: videoToBeDeletedPath,
});
}

await EncodedVideo.findOneAndUpdate(
{
fileName: videoToBeDeletedPath,
},
{
$inc: {
numberOfUses: -1,
},
}
);
};
15 changes: 15 additions & 0 deletions src/utilities/encodedVideoStorage/encodedVideoExtensionCheck.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const encodedVideoExtentionCheck = (encodedUrl: string): boolean => {
const extension = encodedUrl.substring(
"data:".length,
encodedUrl.indexOf(";base64")
);

console.log(extension);

const isValidVideo = extension === "video/mp4";
if (isValidVideo) {
return true;
}

return false;
};
72 changes: 72 additions & 0 deletions src/utilities/encodedVideoStorage/uploadEncodedVideo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import shortid from "shortid";
import * as fs from "fs";
import { writeFile } from "fs/promises";
import { encodedVideoExtentionCheck } from "./encodedVideoExtensionCheck";
import { errors, requestContext } from "../../libraries";
import { INVALID_FILE_TYPE } from "../../constants";
import { EncodedVideo } from "../../models/EncodedVideo";
import path from "path";
import { deletePreviousVideo } from "./deletePreviousVideo";

export const uploadEncodedVideo = async (
encodedVideoURL: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
previousVideoPath?: string | null
): Promise<string> => {
const isURLValidVideo = encodedVideoExtentionCheck(encodedVideoURL);

if (!isURLValidVideo) {
throw new errors.InvalidFileTypeError(
requestContext.translate(INVALID_FILE_TYPE.MESSAGE),
INVALID_FILE_TYPE.CODE,
INVALID_FILE_TYPE.PARAM
);
}

const encodedVideoAlreadyExist = await EncodedVideo.findOne({
content: encodedVideoURL,
});

if (previousVideoPath) {
await deletePreviousVideo(previousVideoPath);
}

if (encodedVideoAlreadyExist) {
await EncodedVideo.findOneAndUpdate(
{
content: encodedVideoURL,
},
{
$inc: {
numberOfUses: 1,
},
}
);
return encodedVideoAlreadyExist.fileName;
}

let id = shortid.generate();

id = "videos/" + id + "video.mp4";

const uploadedEncodedVideo = await EncodedVideo.create({
fileName: id,
content: encodedVideoURL,
});

const data = encodedVideoURL.replace(/^data:video\/\w+;base64,/, "");

const buf = Buffer.from(data, "base64");

if (!fs.existsSync(path.join(__dirname, "../../../videos"))) {
fs.mkdir(path.join(__dirname, "../../../videos"), (error) => {
if (error) {
throw error;
}
});
}

await writeFile(path.join(__dirname, "../../../" + id), buf);

return uploadedEncodedVideo.fileName;
};
Loading

0 comments on commit 3973d69

Please sign in to comment.