Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@
"@nestjs/mongoose": "^11.0.3",
"@nestjs/platform-express": "^11.0.1",
"bcrypt": "^6.0.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"mongoose": "^8.18.1",
"nodemailer": "^7.0.10",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
},
Expand Down
5 changes: 4 additions & 1 deletion backend/src/api/article/article.module.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
// article.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { ConfigModule } from '@nestjs/config';
import { ArticleController } from './article.controller';
import { ArticleService } from './article.service';
import { Article, ArticleSchema } from './article.schema';
import { EmailService } from '../../services/email.service';

@Module({
imports: [
MongooseModule.forFeature([{ name: Article.name, schema: ArticleSchema }]),
ConfigModule,
],
controllers: [ArticleController],
providers: [ArticleService],
providers: [ArticleService, EmailService],
})
export class ArticleModule {}
21 changes: 21 additions & 0 deletions backend/src/api/article/article.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { getModelToken } from '@nestjs/mongoose';
import { ArticleService } from './article.service';
import { Article, ArticleStatus, EvidenceType } from './article.schema';
import { Model } from 'mongoose';
import { EmailService } from '../../services/email.service';
import { ConfigService } from '@nestjs/config';

// Mock Article model
const mockArticleModel = {
Expand All @@ -16,6 +18,17 @@ const mockArticleModel = {
exec: jest.fn(),
};

// Mock EmailService
const mockEmailService = {
sendMail: jest.fn().mockResolvedValue(true),
sendBulkMail: jest.fn().mockResolvedValue(true),
};

// Mock ConfigService
const mockConfigService = {
get: jest.fn(),
};

describe('ArticleService', () => {
let service: ArticleService;
let model: Model<Article>;
Expand All @@ -28,6 +41,14 @@ describe('ArticleService', () => {
provide: getModelToken(Article.name),
useValue: mockArticleModel,
},
{
provide: EmailService,
useValue: mockEmailService,
},
{
provide: ConfigService,
useValue: mockConfigService,
},
],
}).compile();

Expand Down
101 changes: 99 additions & 2 deletions backend/src/api/article/article.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Article, ArticleDocument, ArticleStatus } from './article.schema';
import { CreateArticleDto, ReviewArticleDto } from './create-article.dto';
import { EmailService } from '../../services/email.service';

@Injectable()
export class ArticleService {
constructor(
@InjectModel(Article.name) private articleModel: Model<ArticleDocument>,
private emailService: EmailService,
) {}

// Get all articles (with optional filtering)
Expand Down Expand Up @@ -39,7 +41,10 @@ export class ArticleService {
}

// Find articles with similar DOIs (for duplicate checking)
async findArticlesBySimilarDOI(doi: string, excludeId?: string): Promise<Article[]> {
async findArticlesBySimilarDOI(
doi: string,
excludeId?: string,
): Promise<Article[]> {
// Query object to build the search criteria
const query: any = { doi };

Expand Down Expand Up @@ -175,7 +180,10 @@ export class ArticleService {
updateData['isDuplicate'] = true;
// Prevent an article from being marked as duplicate of itself
if (reviewData.duplicateOf === id) {
throw new HttpException('Cannot mark article as duplicate of itself', HttpStatus.BAD_REQUEST);
throw new HttpException(
'Cannot mark article as duplicate of itself',
HttpStatus.BAD_REQUEST,
);
}
updateData['duplicateOf'] = reviewData.duplicateOf;
} else {
Expand All @@ -195,9 +203,98 @@ export class ArticleService {
);
}

// Send email notification to submitter
console.log(`Attempting to send notification email for article ${updatedArticle.customId} with status ${updatedArticle.status}`);
await this.sendReviewNotificationEmail(updatedArticle);

return updatedArticle;
}

// Send email notification to submitter about article review status
private async sendReviewNotificationEmail(article: Article) {
console.log(`Processing notification for article ${article.customId} with status ${article.status} and submitter email ${article.submitterEmail}`);

if (!article.submitterEmail) {
console.warn(`No submitter email found for article ${article.customId}`);
return;
}

let subject = '';
let htmlContent = '';

// Determine the content based on the status
if (article.status === ArticleStatus.APPROVED) {
subject = 'Your Article Has Been Approved - CISE_SPEED';
htmlContent = `
<h2>Article Review Result Notification</h2>
<p>Hello,</p>
<p>Your article <strong>"${article.title}"</strong> has been approved and added to the database.</p>
<p><strong>Article Details:</strong></p>
<ul>
<li>Title: ${article.title}</li>
<li>ID: ${article.customId}</li>
<li>Authors: ${article.authors}</li>
<li>Publication Year: ${article.pubyear}</li>
<li>Evidence Type: ${article.evidence}</li>
</ul>
<p>Thank you for contributing to the CISE_SPEED database!</p>
<p>If you have any questions, please feel free to contact us.</p>
`;
} else if (article.status === ArticleStatus.REJECTED) {
subject = 'Article Review Result - CISE_SPEED';
htmlContent = `
<h2>Article Review Result Notification</h2>
<p>Hello,</p>
<p>Unfortunately, your article <strong>"${article.title}"</strong> did not pass the review.</p>

${article.reviewComment ? `<p><strong>Review Comment:</strong> ${article.reviewComment}</p>` : ''}

<p><strong>Article Details:</strong></p>
<ul>
<li>Title: ${article.title}</li>
<li>ID: ${article.customId}</li>
<li>Authors: ${article.authors}</li>
<li>Publication Year: ${article.pubyear}</li>
</ul>
<p>Thank you for your interest and contribution to the CISE_SPEED database!</p>
<p>If you have any questions, please feel free to contact us.</p>
`;
} else {
// For other status changes (like back to PENDING), we could send a generic notification
console.log(
`Article ${article.customId} status changed to ${article.status}, no email notification required.`,
);
return;
}

console.log(`Attempting to send email to ${article.submitterEmail} with subject: ${subject}`);

try {
const result = await this.emailService.sendMail(
article.submitterEmail,
subject,
htmlContent,
`CISE_SPEED: Your article "${article.title}" has been reviewed`, // text
'CISE_SPEED System', // fromName
);

if (result) {
console.log(
`Notification email sent successfully to ${article.submitterEmail} for article ${article.customId}`,
);
} else {
console.error(
`Failed to send notification email to ${article.submitterEmail} for article ${article.customId}`,
);
}
} catch (error) {
console.error(
`Error sending notification email to ${article.submitterEmail} for article ${article.customId}:`,
error,
);
}
}

// Search articles by keywords and filters with sorting
async searchArticles(
keywords: string,
Expand Down
2 changes: 2 additions & 0 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import { MongooseModule } from '@nestjs/mongoose';
import { ConfigModule } from '@nestjs/config';
import { ArticleModule } from './api/article/article.module';
import { UserModule } from './api/user/user.module';
import { EmailModule } from './services/email.module';

@Module({
imports: [
ConfigModule.forRoot(),
MongooseModule.forRoot(process.env.DB_URI || ''),
ArticleModule,
UserModule,
EmailModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
Loading