Skip to content

Commit

Permalink
Merge pull request #835 from bounswe/feature/BE/824/semantic-search
Browse files Browse the repository at this point in the history
Feature/be/824/semantic search
  • Loading branch information
melihgezerr authored Dec 25, 2023
2 parents 2102058 + 443d53e commit 8e001fa
Show file tree
Hide file tree
Showing 11 changed files with 448 additions and 5 deletions.
19 changes: 19 additions & 0 deletions ludos/backend/src/controllers/search.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '@nestjs/swagger';
import { AuthorizedRequest } from '../interfaces/common/authorized-request.interface';
import { SerializerInterceptor } from '../interceptors/customSerializer.interceptor';
import { SemanticSearchResponseDto } from '../dtos/search/response/semantic-search.response.dto';

@Controller('search')
@ApiTags('search')
Expand All @@ -33,4 +34,22 @@ export class SearchController {
req.user ? req.user.id : undefined,
);
}
@ApiOkResponse({
type: SemanticSearchResponseDto,
})
@ApiBearerAuth()
@ApiOperation({
summary: 'Semantic Search for users, games, groups and posts',
})
@UseInterceptors(new SerializerInterceptor(SemanticSearchResponseDto))
@Get('/semantic/:searchKey')
async semanticSearch(
@Req() req: AuthorizedRequest,
@Param('searchKey') searchKey: string,
): Promise<SemanticSearchResponseDto> {
return await this.searchService.semanticSearch(
searchKey,
req.user ? req.user.id : undefined,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { ApiProperty } from '@nestjs/swagger';
import { GameListResponseDto } from '../../game/response/list.response';
import { PostListResponseDto } from '../../post/response/list.response.dto';
import { UserInOtherResponsesDto } from '../../user/response/user-in-other-responses.dto';
import { Expose, Type } from 'class-transformer';
import { GroupListResponseDto } from '../../group/response/list.response.dto';
export class UserSemanticResponseDto {
@ApiProperty({ type: () => UserInOtherResponsesDto })
@Expose()
@Type(() => UserInOtherResponsesDto)
item: UserInOtherResponsesDto;

@ApiProperty()
@Expose()
score: number;
}
export class GameSemanticResponseDto {
@ApiProperty({ type: () => GameListResponseDto })
@Expose()
@Type(() => GameListResponseDto)
item: GameListResponseDto;

@ApiProperty()
@Expose()
score: number;
}
export class PostSemanticResponseDto {
@ApiProperty({ type: () => PostListResponseDto })
@Expose()
@Type(() => PostListResponseDto)
item: PostListResponseDto;

@ApiProperty()
@Expose()
score: number;
}
export class GroupSemanticResponseDto {
@ApiProperty({ type: () => GroupListResponseDto })
@Expose()
@Type(() => GroupListResponseDto)
item: GroupListResponseDto;

@ApiProperty()
@Expose()
score: number;
}
export class SemanticSearchResponseDto {
@ApiProperty({ type: () => [UserSemanticResponseDto] })
@Type(() => UserSemanticResponseDto)
@Expose()
users: UserSemanticResponseDto[];
@ApiProperty({ type: () => [GameSemanticResponseDto] })
@Type(() => GameSemanticResponseDto)
@Expose()
games: GameSemanticResponseDto[];
@ApiProperty({ type: () => [PostSemanticResponseDto] })
@Type(() => PostSemanticResponseDto)
@Expose()
posts: PostSemanticResponseDto[];
@ApiProperty({ type: () => [GroupSemanticResponseDto] })
@Type(() => GroupSemanticResponseDto)
@Expose()
groups: GroupSemanticResponseDto[];
}
4 changes: 4 additions & 0 deletions ludos/backend/src/interfaces/semantic/response.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface SemanticResponse {
id: string;
score: number;
}
4 changes: 2 additions & 2 deletions ludos/backend/src/services/config/typeorm-config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ export class TypeOrmConfigService implements TypeOrmOptionsFactory {
ssl: {
rejectUnauthorized: false,
},
},
},
}),

entities: [
User,
ResetPassword,
Expand Down
238 changes: 235 additions & 3 deletions ludos/backend/src/services/search.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { Injectable } from '@nestjs/common';
import { UserRepository } from '../repositories/user.repository';
import { GameRepository } from '../repositories/game.repository';
import { PostRepository } from '../repositories/post.repository';
import axios from 'axios';
import { SearchResponseDto } from '../dtos/search/response/search.response.dto';
import {
GameSemanticResponseDto,
GroupSemanticResponseDto,
PostSemanticResponseDto,
SemanticSearchResponseDto,
UserSemanticResponseDto,
} from '../dtos/search/response/semantic-search.response.dto';
import { SemanticResponse } from '../interfaces/semantic/response.interface';
import { GameRepository } from '../repositories/game.repository';
import { GroupRepository } from '../repositories/group.repository';
import { PostRepository } from '../repositories/post.repository';
import { UserRepository } from '../repositories/user.repository';

@Injectable()
export class SearchService {
Expand Down Expand Up @@ -55,4 +64,227 @@ export class SearchService {
groups: groups.items,
};
}
public async semanticSearch(
searchKey: string,
userId?: string,
): Promise<SemanticSearchResponseDto> {
const users = await this.userRepository.findUsers(1, 1000);
const games = await this.gameRepository.findGames(
1,
1000,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
userId,
);
const posts = await this.postRepository.findPosts(
1,
1000,
undefined,
undefined,
undefined,
undefined,
userId,
);
const groups = await this.groupRepository.findGroups(
1,
1000,
undefined,
undefined,
undefined,
undefined,
userId,
);
let usersUsernameSemanticSearchResult: SemanticResponse[] = [];
try {
usersUsernameSemanticSearchResult = (
await axios.post(`http://104.248.19.88:8000/search/${searchKey}`, {
items: users.items.map((user) => {
return {
id: user.id,
text: user.username,
};
}),
})
).data;
} catch (error) {
usersUsernameSemanticSearchResult = [];
}

const items = [];
users.items.forEach((user) => {
if (user.fullName != null) {
items.push({
id: user.id,
text: user.fullName,
});
}
});
let usersFullNameSemanticSearchResult: SemanticResponse[] = [];
try {
usersFullNameSemanticSearchResult = (
await axios.post(`http://104.248.19.88:8000/search/${searchKey}`, {
items: items,
})
).data;
} catch (error) {
usersFullNameSemanticSearchResult = [];
}
let gamesTitleSemanticSearchResult: SemanticResponse[] = [];
try {
gamesTitleSemanticSearchResult = (
await axios.post(`http://104.248.19.88:8000/search/${searchKey}`, {
items: games.items.map((game) => {
return {
id: game.id,
text: game.title,
};
}),
})
).data;
} catch (error) {
gamesTitleSemanticSearchResult = [];
}
let postsTitleSemanticSearchResult: SemanticResponse[] = [];
try {
postsTitleSemanticSearchResult = (
await axios.post(`http://104.248.19.88:8000/search/${searchKey}`, {
items: posts.items.map((post) => {
return {
id: post.id,
text: post.title,
};
}),
})
).data;
} catch (error) {
postsTitleSemanticSearchResult = [];
}
let postsBodySemanticSearchResult: SemanticResponse[] = [];
try {
postsBodySemanticSearchResult = (
await axios.post(`http://104.248.19.88:8000/search/${searchKey}`, {
items: posts.items.map((post) => {
return {
id: post.id,
text: post.body,
};
}),
})
).data;
} catch (error) {
postsBodySemanticSearchResult = [];
}
let groupsNameSemanticSearchResult: SemanticResponse[] = [];
try {
groupsNameSemanticSearchResult = (
await axios.post(`http://104.248.19.88:8000/search/${searchKey}`, {
items: groups.items.map((group) => {
return {
id: group.id,
text: group.name,
};
}),
})
).data;
} catch (error) {
groupsNameSemanticSearchResult = [];
}

const groupsResponse: GroupSemanticResponseDto[] = [];
groupsNameSemanticSearchResult.forEach((group: SemanticResponse) => {
if (group.score > 0.5) {
groupsResponse.push({
item: groups.items.find((g) => g.id === group.id),
score: group.score,
});
}
});
const gamesResponse: GameSemanticResponseDto[] = [];
gamesTitleSemanticSearchResult.forEach((game: SemanticResponse) => {
if (game.score > 0.5) {
gamesResponse.push({
item: games.items.find((g) => g.id === game.id),
score: game.score,
});
}
});
const usersResponse: UserSemanticResponseDto[] = [];
usersUsernameSemanticSearchResult.forEach((user: SemanticResponse) => {
if (user.score > 0.5) {
const sameUser = usersFullNameSemanticSearchResult.find(
(u) => u.id === user.id,
);
if (sameUser) {
if (sameUser.score > user.score) {
usersResponse.push({
item: users.items.find((u) => u.id === user.id),
score: sameUser.score,
});
usersFullNameSemanticSearchResult.splice(
usersFullNameSemanticSearchResult.indexOf(sameUser),
1,
);
return;
}
}
usersResponse.push({
item: users.items.find((u) => u.id === user.id),
score: user.score,
});
}
});
usersFullNameSemanticSearchResult.forEach((user: SemanticResponse) => {
if (user.score > 0.5) {
usersResponse.push({
item: users.items.find((u) => u.id === user.id),
score: user.score,
});
}
});
const postsResponse: PostSemanticResponseDto[] = [];
postsTitleSemanticSearchResult.forEach((post: SemanticResponse) => {
if (post.score > 0.5) {
const samePost = postsBodySemanticSearchResult.find(
(p) => p.id === post.id,
);
if (samePost) {
if (samePost.score > post.score) {
postsResponse.push({
item: posts.items.find((p) => p.id === post.id),
score: samePost.score,
});
postsBodySemanticSearchResult.splice(
postsBodySemanticSearchResult.indexOf(samePost),
1,
);
return;
}
}
postsResponse.push({
item: posts.items.find((p) => p.id === post.id),
score: post.score,
});
}
});
postsBodySemanticSearchResult.forEach((post: SemanticResponse) => {
if (post.score > 0.5) {
postsResponse.push({
item: posts.items.find((p) => p.id === post.id),
score: post.score,
});
}
});

return {
users: usersResponse,
games: gamesResponse,
posts: postsResponse,
groups: groupsResponse,
};
}
}
Loading

0 comments on commit 8e001fa

Please sign in to comment.