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
25 changes: 25 additions & 0 deletions src/post/dto/search-posts.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,17 @@ import {
Max,
Min,
MinLength,
IsDateString,
} from 'class-validator';
import { Type } from 'class-transformer';
import { PaginationDto } from 'src/common/dto/pagination.dto';
import { PostType } from '@prisma/client';

export enum SearchOrderBy {
RELEVANCE = 'relevance',
LATEST = 'latest',
}

export class SearchPostsDto extends PaginationDto {
@ApiProperty({
description: 'Search query to match against post content (supports partial matching)',
Expand Down Expand Up @@ -50,4 +56,23 @@ export class SearchPostsDto extends PaginationDto {
@Min(0)
@Max(1)
similarityThreshold?: number = 0.1;

@ApiPropertyOptional({
description: 'Filter posts created before this date (ISO 8601 format)',
example: '2024-12-01T00:00:00Z',
})
@IsOptional()
@IsDateString()
before_date?: string;

@ApiPropertyOptional({
description: 'Order search results by relevance or latest',
enum: SearchOrderBy,
example: SearchOrderBy.RELEVANCE,
})
@IsOptional()
@IsEnum(SearchOrderBy, {
message: `order_by must be one of: ${Object.values(SearchOrderBy).join(', ')}`,
})
order_by?: SearchOrderBy = SearchOrderBy.RELEVANCE;
}
108 changes: 108 additions & 0 deletions src/post/dto/search-response.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { ApiProperty } from '@nestjs/swagger';
import { FeedPostDto, MediaDto, OriginalPostDataDto } from './timeline-feed-reponse.dto';

export class SearchPostDto {
@ApiProperty({ example: 1, description: 'User ID of the author' })
userId: number;

@ApiProperty({ example: 'johndoe', description: 'Username of the author' })
username: string;

@ApiProperty({ example: true, description: 'Whether the author is verified' })
verified: boolean;

@ApiProperty({ example: 'John Doe', description: 'Display name of the author' })
name: string;

@ApiProperty({
example: 'https://example.com/avatar.jpg',
description: 'Avatar URL',
nullable: true,
})
avatar: string | null;

@ApiProperty({ example: 456, description: 'Post ID' })
postId: number;

@ApiProperty({ example: '2023-11-21T12:00:00Z', description: 'Post creation date' })
date: Date;

@ApiProperty({ example: 200, description: 'Number of likes' })
likesCount: number;

@ApiProperty({ example: 75, description: 'Number of reposts' })
retweetsCount: number;

@ApiProperty({ example: 30, description: 'Number of replies' })
commentsCount: number;

@ApiProperty({ example: true, description: 'Whether current user liked this post' })
isLikedByMe: boolean;

@ApiProperty({ example: false, description: 'Whether current user follows the author' })
isFollowedByMe: boolean;

@ApiProperty({ example: false, description: 'Whether current user reposted this post' })
isRepostedByMe: boolean;

@ApiProperty({ example: 'This is a post content', description: 'Post content' })
text: string;

@ApiProperty({ type: [MediaDto], description: 'Media attachments' })
media: MediaDto[];

@ApiProperty({ example: false, description: 'Whether this is a repost' })
isRepost: boolean;

@ApiProperty({ example: false, description: 'Whether this is a quote tweet' })
isQuote: boolean;

@ApiProperty({
type: OriginalPostDataDto,
description: 'Original post information (for quotes and reposts)',
nullable: true,
required: false,
})
originalPostData?: OriginalPostDataDto;
}

export class SearchPostsDataDto {
@ApiProperty({
type: [SearchPostDto],
description: 'Array of posts matching search criteria',
})
posts: SearchPostDto[];
}

export class SearchPostsResponseDto {
@ApiProperty({ example: 'success', description: 'Response status' })
status: string;

@ApiProperty({
example: 'Search results retrieved successfully',
description: 'Response message',
})
message: string;

@ApiProperty({
type: SearchPostsDataDto,
description: 'Search results data',
})
data: SearchPostsDataDto;

@ApiProperty({
description: 'Pagination metadata',
example: {
totalItems: 100,
page: 1,
limit: 10,
totalPages: 10,
},
})
metadata: {
totalItems: number;
page: number;
limit: number;
totalPages: number;
};
}
37 changes: 22 additions & 15 deletions src/post/post.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
} from './dto/like-response.dto';
import { ToggleRepostResponseDto, GetRepostersResponseDto } from './dto/repost-response.dto';
import { SearchByHashtagResponseDto } from './dto/hashtag-search-response.dto';
import { SearchPostsResponseDto } from './dto/search-response.dto';
import { ErrorResponseDto } from 'src/common/dto/error-response.dto';
import { JwtAuthGuard } from 'src/auth/guards/jwt-auth/jwt-auth.guard';

Expand Down Expand Up @@ -71,7 +72,7 @@ export class PostController {
private readonly repostService: RepostService,
@Inject(Services.MENTION)
private readonly mentionService: MentionService,
) { }
) {}

@Post()
@UseGuards(JwtAuthGuard)
Expand Down Expand Up @@ -212,6 +213,20 @@ export class PostController {
description: 'Minimum similarity threshold (0.0 to 1.0). Lower values return more results.',
example: 0.1,
})
@ApiQuery({
name: 'before_date',
required: false,
type: String,
description: 'Filter posts created before this date (ISO 8601 format)',
example: '2024-12-01T00:00:00Z',
})
@ApiQuery({
name: 'order_by',
required: false,
enum: ['relevance', 'latest'],
description: 'Order search results by relevance (default) or latest (created_at desc)',
example: 'relevance',
})
@ApiQuery({
name: 'page',
required: false,
Expand All @@ -229,7 +244,7 @@ export class PostController {
@ApiResponse({
status: HttpStatus.OK,
description: 'Search results retrieved successfully',
type: GetPostsResponseDto,
type: SearchPostsResponseDto,
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
Expand All @@ -250,7 +265,7 @@ export class PostController {
return {
status: 'success',
message: 'Search results retrieved successfully',
data: posts,
data: { posts },
metadata: {
totalItems,
page,
Expand Down Expand Up @@ -306,7 +321,7 @@ export class PostController {
@ApiResponse({
status: HttpStatus.OK,
description: 'Posts with hashtag retrieved successfully',
type: SearchByHashtagResponseDto,
type: SearchPostsResponseDto,
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
Expand All @@ -330,7 +345,7 @@ export class PostController {
return {
status: 'success',
message: `Posts with hashtag #${hashtag} retrieved successfully`,
data: posts,
data: { posts },
metadata: {
hashtag,
totalItems,
Expand Down Expand Up @@ -1036,11 +1051,7 @@ export class PostController {
@Query('page') page: number = 1,
@Query('limit') limit: number = 10,
) {
const posts = await this.postService.getUserPosts(
userId,
+page,
+limit,
);
const posts = await this.postService.getUserPosts(userId, +page, +limit);

return {
status: 'success',
Expand Down Expand Up @@ -1091,11 +1102,7 @@ export class PostController {
@Query('page') page: number = 1,
@Query('limit') limit: number = 10,
) {
const replies = await this.postService.getUserReplies(
userId,
+page,
+limit,
);
const replies = await this.postService.getUserReplies(userId, +page, +limit);

return {
status: 'success',
Expand Down
Loading
Loading