Skip to content

Commit

Permalink
Library file added
Browse files Browse the repository at this point in the history
  • Loading branch information
ojasaklechayt committed Feb 25, 2024
1 parent 30dfe0f commit c98ec1b
Show file tree
Hide file tree
Showing 11 changed files with 1,917 additions and 0 deletions.
267 changes: 267 additions & 0 deletions lib/actions/answer.action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
"use server";

import { redirect } from "next/navigation";
import { revalidatePath } from "next/cache";

import Answer from "@/database/answer.model";
import Question from "@/database/question.model";
import User from "@/database/user.model";
import Interaction from "@/database/interaction.model";

import { connectToDatabase } from "@/lib/mongoose";

import type {
AnswerVoteParams,
CreateAnswerParams,
DeleteAnswerParams,
EditAnswerParams,
GetAnswerByIdParams,
GetAnswersParams,
} from "./shared.types";

export async function createAnswer(params: CreateAnswerParams) {
try {
connectToDatabase();

const { content, author, question, path } = params;

const newAnswer = await Answer.create({
content,
author,
question,
path,
});

// add the answer to the question's answers array
const questionObj = await Question.findByIdAndUpdate(question, {
$push: { answers: newAnswer._id },
});

// create an interaction record for the user's create_answer action
await Interaction.create({
user: author,
action: "answer",
question,
answer: newAnswer._id,
tags: questionObj.tag,
});

// increment author's reputation by +S for creating a answer
await User.findByIdAndUpdate(author, { $inc: { reputation: 10 } });

revalidatePath(path);
} catch (error) {
console.log(error);
throw error;
}
}

export async function editAnswer(params: EditAnswerParams) {
try {
connectToDatabase();

const { answerId, content, path } = params;

const answer = await Answer.findById(answerId);

if (!answer) {
throw new Error("Answer not found");
}

answer.content = content;

await answer.save();

redirect(path);
} catch (error) {
console.log(error);
throw error;
}
}

export async function deleteAnswer(params: DeleteAnswerParams) {
try {
connectToDatabase();

const { answerId, path } = params;

const answer = await Answer.findById(answerId);

if (!answer) {
throw new Error("Answer not found");
}

await answer.deleteOne({ _id: answerId });

await Question.updateMany(
{ _id: answer.question },
{ $pull: { answers: answerId } }
);

await Interaction.deleteMany({ answer: answerId });

revalidatePath(path);
} catch (error) {
console.log(error);
throw error;
}
}

export async function getAnswers(params: GetAnswersParams) {
try {
connectToDatabase();

const { questionId, page = 1, pageSize = 10, sortBy } = params;

// Calculate the number of answers to skip based on the page number and page size
const skipAmount = (page - 1) * pageSize;

let sortOptions = {};

switch (sortBy) {
case "highestUpvotes":
sortOptions = { upvotes: -1 };
break;
case "lowestUpvotes":
sortOptions = { upvotes: 1 };
break;
case "recent":
sortOptions = { createdAt: -1 };
break;
case "old":
sortOptions = { createdAt: 1 };
break;
default:
break;
}

const answers = await Answer.find({ question: questionId })
.populate("author", "_id clerkId name picture")
.sort(sortOptions)
.skip(skipAmount)
.limit(pageSize);

const totalAnswers = await Answer.countDocuments({ question: questionId });

const isNext = totalAnswers > skipAmount + answers.length;

return { answers, isNext };
} catch (error) {
console.log(error);
throw error;
}
}

export async function getAnswerById(params: GetAnswerByIdParams) {
try {
connectToDatabase();

const { answerId } = params;

const answer = await Answer.findById(answerId).populate(
"author",
"_id clerkId name picture"
);

return answer;
} catch (error) {
console.log(error);
throw error;
}
}

export async function upvoteAnswer(params: AnswerVoteParams) {
try {
connectToDatabase();

const { answerId, userId, hasupVoted, hasdownVoted, path } = params;

let updateQuery = {};

if (hasupVoted) {
updateQuery = {
$pull: { upvotes: userId },
};
} else if (hasdownVoted) {
updateQuery = {
$pull: { downvotes: userId },
$push: { upvotes: userId },
};
} else {
updateQuery = { $addToSet: { upvotes: userId } };
}

const answer = await Answer.findByIdAndUpdate(answerId, updateQuery, {
new: true,
});

if (!answer) {
throw new Error("Answer not found");
}

if (userId !== answer.author.toString()) {
// increment user's reputation by +S for upvoting/revoking an upvote to the answer (S = 2)
await User.findByIdAndUpdate(userId, {
$inc: { reputation: hasupVoted ? -2 : 2 },
});

// increment author's reputation by +S for upvoting/revoking an upvote to the answer (S = 10)
await User.findByIdAndUpdate(answer.author, {
$inc: { reputation: hasupVoted ? -10 : 10 },
});
}

revalidatePath(path);
} catch (error) {
console.log(error);
throw error;
}
}

export async function downvoteAnswer(params: AnswerVoteParams) {
try {
connectToDatabase();

const { answerId, userId, hasupVoted, hasdownVoted, path } = params;

let updateQuery = {};

if (hasdownVoted) {
updateQuery = {
$pull: { downvotes: userId },
};
} else if (hasupVoted) {
updateQuery = {
$pull: { upvotes: userId },
$push: { downvotes: userId },
};
} else {
updateQuery = { $addToSet: { downvotes: userId } };
}

const answer = await Question.findByIdAndUpdate(answerId, updateQuery, {
new: true,
});

if (!answer) {
throw new Error("Answer not found");
}

if (userId !== answer.author.toString()) {
// decrement author's reputation by +S for downvoting/revoking an downvote to the answer (S = 2)
await User.findByIdAndUpdate(userId, {
$inc: { reputation: hasdownVoted ? -2 : 2 },
});

// decrement author's reputation by +S for downvoting/revoking an downvote to the answer (S = 10)
await User.findByIdAndUpdate(answer.author, {
$inc: { reputation: hasdownVoted ? -10 : 10 },
});
}

revalidatePath(path);
} catch (error) {
console.log(error);
throw error;
}
}
90 changes: 90 additions & 0 deletions lib/actions/general.action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"use server";

import Question from "@/database/question.model";
import User from "@/database/user.model";
import Answer from "@/database/answer.model";
import Tag from "@/database/tag.model";

import { connectToDatabase } from "../mongoose";
import { SearchParams } from "./shared.types";

const SearchableTypes = ["question", "user", "answer", "tag"];

export async function globalSearch(params: SearchParams) {
try {
connectToDatabase();

const { query, type } = params;
const regexQuery = { $regex: query, $options: "i" };

let results = [];

const modelsAndTypes = [
{ model: Question, searchField: "title", type: "question" },
{ model: User, searchField: "name", type: "user" },
{ model: Answer, searchField: "content", type: "answer" },
{ model: Tag, searchField: "name", type: "tag" },
];

const typeLower = type?.toLowerCase();

if (!typeLower || !SearchableTypes.includes(typeLower)) {
// Search across all types

for (const { model, searchField, type } of modelsAndTypes) {
const queryResults = await model
.find({ [searchField]: regexQuery })
.limit(8);

results.push(
...queryResults.map((item) => ({
title:
type === "answer"
? `Answer containing "${query}"`
: item[searchField],
type,
id:
type === "user"
? item.clerkId
: type === "answer"
? [item.question, item._id]
: item._id,
}))
);
}
} else {
// Search only in the specified model type

const modelInfo = modelsAndTypes.find((item) => item.type === type);

if (!modelInfo) {
throw new Error("Invalid type specified");
}

const queryResults = await modelInfo.model
.find({
[modelInfo.searchField]: regexQuery,
})
.limit(8);

results = queryResults.map((item) => ({
title:
type === "answer"
? `Answers containing "${query}"`
: item[modelInfo.searchField],
type,
id:
type === "user"
? item.clerkId
: type === "answer"
? [item.question, item._id]
: item._id,
}));
}

return JSON.stringify(results);
} catch (error: any) {
console.log(`Error fetching the global results: ${error}`);
throw error;
}
}
38 changes: 38 additions & 0 deletions lib/actions/interaction.action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"use server";

import Question from "@/database/question.model";
import Interaction from "@/database/interaction.model";

import { connectToDatabase } from "@/lib/mongoose";

import type { ViewQuestionParams } from "./shared.types";

export async function viewQuestion(params: ViewQuestionParams) {
try {
connectToDatabase();

const { questionId, userId } = params;

// update view count for the question
await Question.findByIdAndUpdate(questionId, { $inc: { views: 1 } });

if (userId) {
const existingInteraction = await Interaction.findOne({
user: userId,
action: "view",
question: questionId,
});

if (existingInteraction) return console.log("User has already viewed.");

await Interaction.create({
user: userId,
action: "view",
question: questionId,
});
}
} catch (error) {
console.log(error);
throw error;
}
}
Loading

0 comments on commit c98ec1b

Please sign in to comment.