From 04ba49b13d20d352fead4de736e0e6f7df848fba Mon Sep 17 00:00:00 2001 From: Anu Viswan Date: Mon, 4 Aug 2025 21:39:09 +0530 Subject: [PATCH 01/19] Adding avataar card --- .../components/private/reviews/ReviewCard.vue | 13 +- .../components/private/search/SearchUsers.vue | 114 +++++------ .../components/private/user/AvataarCard.vue | 180 +++++++++--------- .../private/user/UserProfileCard.vue | 148 +++++++------- .../nt/src/types/UserTypes.ts | 4 +- 5 files changed, 236 insertions(+), 223 deletions(-) diff --git a/client/nt.webclient/vue3withtypescript/nt/src/components/private/reviews/ReviewCard.vue b/client/nt.webclient/vue3withtypescript/nt/src/components/private/reviews/ReviewCard.vue index 6f47d897..1c45bd02 100644 --- a/client/nt.webclient/vue3withtypescript/nt/src/components/private/reviews/ReviewCard.vue +++ b/client/nt.webclient/vue3withtypescript/nt/src/components/private/reviews/ReviewCard.vue @@ -1,6 +1,7 @@ diff --git a/client/nt.webclient/vue3withtypescript/nt/src/components/private/user/AvataarCard.vue b/client/nt.webclient/vue3withtypescript/nt/src/components/private/user/AvataarCard.vue index 5969c499..f3f891eb 100644 --- a/client/nt.webclient/vue3withtypescript/nt/src/components/private/user/AvataarCard.vue +++ b/client/nt.webclient/vue3withtypescript/nt/src/components/private/user/AvataarCard.vue @@ -29,110 +29,112 @@ diff --git a/client/nt.webclient/vue3withtypescript/nt/src/components/private/user/UserProfileCard.vue b/client/nt.webclient/vue3withtypescript/nt/src/components/private/user/UserProfileCard.vue index 0726b964..a4afc72c 100644 --- a/client/nt.webclient/vue3withtypescript/nt/src/components/private/user/UserProfileCard.vue +++ b/client/nt.webclient/vue3withtypescript/nt/src/components/private/user/UserProfileCard.vue @@ -126,80 +126,80 @@ diff --git a/client/nt.webclient/vue3withtypescript/nt/src/types/UserTypes.ts b/client/nt.webclient/vue3withtypescript/nt/src/types/UserTypes.ts index 2e77308a..16ddd571 100644 --- a/client/nt.webclient/vue3withtypescript/nt/src/types/UserTypes.ts +++ b/client/nt.webclient/vue3withtypescript/nt/src/types/UserTypes.ts @@ -1,4 +1,4 @@ -import { Review } from "@/types/ReviewTypes"; +import { Review } from '@/types/ReviewTypes'; export interface User { userName: string; displayName?: string; @@ -9,5 +9,5 @@ export interface User { reviews?: Review; Uprated?: 0; Downrated?: 0; - followers:string[]; + followers: string[]; } From 25907a1344b818b0bc444519648b3b1ecf5c6ba3 Mon Sep 17 00:00:00 2001 From: Anu Viswan Date: Mon, 4 Aug 2025 21:45:07 +0530 Subject: [PATCH 02/19] working on ui --- .../nt/src/components/private/reviews/ReviewCard.vue | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/client/nt.webclient/vue3withtypescript/nt/src/components/private/reviews/ReviewCard.vue b/client/nt.webclient/vue3withtypescript/nt/src/components/private/reviews/ReviewCard.vue index 1c45bd02..e2118c4e 100644 --- a/client/nt.webclient/vue3withtypescript/nt/src/components/private/reviews/ReviewCard.vue +++ b/client/nt.webclient/vue3withtypescript/nt/src/components/private/reviews/ReviewCard.vue @@ -17,14 +17,14 @@ Movie Poster
-
+
{{ review.title }}
{{ review.movieTitle }} @@ -34,7 +34,10 @@ {{ review.content }}

-
+ +
⭐ {{ review.rating }} Date: Tue, 5 Aug 2025 07:29:19 +0530 Subject: [PATCH 03/19] working on get user profile --- .../nt/src/apiService/ApiServiceBase.ts | 46 ++--- .../nt/src/apiService/HttpClient.ts | 167 +++++++++--------- .../nt/src/apiService/UserApiService.ts | 163 ++++++++++------- .../components/private/user/AvataarCard.vue | 26 +++ .../src/types/apirequestresponsetypes/User.ts | 6 +- .../infrastructure/nt.gateway/ocelot.json | 21 +++ 6 files changed, 264 insertions(+), 165 deletions(-) diff --git a/client/nt.webclient/vue3withtypescript/nt/src/apiService/ApiServiceBase.ts b/client/nt.webclient/vue3withtypescript/nt/src/apiService/ApiServiceBase.ts index 96ce9b9f..371b3b1e 100644 --- a/client/nt.webclient/vue3withtypescript/nt/src/apiService/ApiServiceBase.ts +++ b/client/nt.webclient/vue3withtypescript/nt/src/apiService/ApiServiceBase.ts @@ -1,26 +1,32 @@ -import { IGraphQlResponseBase, IResponseBase } from "@/types/apirequestresponsetypes/Response"; -import { AxiosRequestConfig } from "axios"; -import HttpClient from "./HttpClient"; +import { + IGraphQlResponseBase, + IResponseBase, +} from '@/types/apirequestresponsetypes/Response'; +import { AxiosRequestConfig } from 'axios'; +import HttpClient from './HttpClient'; import { DocumentNode } from '@apollo/client/core'; +export abstract class ApiServiceBase { + private httpClient: HttpClient; -export abstract class ApiServiceBase -{ - private httpClient : HttpClient; + constructor() { + this.httpClient = new HttpClient(); + } - constructor(){ - this.httpClient = new HttpClient(); - } + protected async invoke( + request: AxiosRequestConfig + ): Promise { + return this.httpClient.invoke(request); + } - protected async invoke(request:AxiosRequestConfig):Promise { - return this.httpClient.invoke(request); - } + protected async getBlob(request: AxiosRequestConfig): Promise { + return this.httpClient.getBlob(request); + } - protected async getBlob(request:AxiosRequestConfig):Promise { - return this.httpClient.getBlob(request); - } - - protected async queryGraphQl(query:DocumentNode,variable:object):Promise{ - return this.httpClient.queryGraphQl(query,variable); - } -} \ No newline at end of file + protected async queryGraphQl( + query: DocumentNode, + variable: object + ): Promise { + return this.httpClient.queryGraphQl(query, variable); + } +} diff --git a/client/nt.webclient/vue3withtypescript/nt/src/apiService/HttpClient.ts b/client/nt.webclient/vue3withtypescript/nt/src/apiService/HttpClient.ts index 4166f444..45442c1f 100644 --- a/client/nt.webclient/vue3withtypescript/nt/src/apiService/HttpClient.ts +++ b/client/nt.webclient/vue3withtypescript/nt/src/apiService/HttpClient.ts @@ -1,97 +1,100 @@ -import { IResponseBase, IGraphQlResponseBase } from "@/types/apirequestresponsetypes/Response"; -import axios, {AxiosInstance, AxiosRequestConfig} from "axios"; -import {useUserStore} from "@/stores/userStore"; +import { + IResponseBase, + IGraphQlResponseBase, +} from '@/types/apirequestresponsetypes/Response'; +import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; +import { useUserStore } from '@/stores/userStore'; import { DocumentNode, gql } from '@apollo/client/core'; -import apolloClient from '@/apolloClient'; +import apolloClient from '@/apolloClient'; -class HttpClient{ +class HttpClient { + private axiosInstance: AxiosInstance; - private axiosInstance : AxiosInstance; - - constructor(){ - const headers = { - //"Access-Control-Allow-Origin": "*", - //"Access-Control-Allow-Headers": "*", // this will allow all CORS requests - //"Access-Control-Allow-Methods": "OPTIONS,POST,GET", // this states the allowed methods - "Content-Type": "application/json", // this shows the expected content type - }; + constructor() { + const headers = { + //"Access-Control-Allow-Origin": "*", + //"Access-Control-Allow-Headers": "*", // this will allow all CORS requests + //"Access-Control-Allow-Methods": "OPTIONS,POST,GET", // this states the allowed methods + 'Content-Type': 'application/json', // this shows the expected content type + }; - console.log('base URL' + import.meta.env.VITE_APP_API_URL) - this.axiosInstance = axios.create({baseURL: import.meta.env.VITE_APP_API_URL, headers:headers}); - - this.axiosInstance.interceptors.request.use(function (config) - { - const userStoreInstance = useUserStore(); - if(userStoreInstance.Token){ - console.log("Submitting with token " + userStoreInstance.Token) - config.headers.Authorization = `Bearer ${userStoreInstance.Token}`; - } - else{ - console.log("Token not available") - } - return config; - }); - } - - public async invoke(config:AxiosRequestConfig):Promise { + console.log('base URL' + import.meta.env.VITE_APP_API_URL); + this.axiosInstance = axios.create({ + baseURL: import.meta.env.VITE_APP_API_URL, + headers: headers, + }); - try{ - const response =await this.axiosInstance.request(config); - console.log(response.data) - return response.data; - }catch(error : unknown){ + this.axiosInstance.interceptors.request.use(function (config) { + const userStoreInstance = useUserStore(); + if (userStoreInstance.Token) { + console.log('Submitting with token ' + userStoreInstance.Token); + config.headers.Authorization = `Bearer ${userStoreInstance.Token}`; + } else { + console.log('Token not available'); + } + return config; + }); + } - if(axios.isAxiosError(error)){ - return { - status : error.response?.status, - hasError : true, - errors : error.response?.data.errors - } - } - else{ - console.log("Some other error ?? " + error); - } - } - return {}; + public async invoke( + config: AxiosRequestConfig + ): Promise { + try { + const response = await this.axiosInstance.request(config); + console.log(response.data); + return response.data; + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + return { + status: error.response?.status, + hasError: true, + errors: error.response?.data.errors, + }; + } else { + console.log('Some other error ?? ' + error); + } } - public async getBlob(config:AxiosRequestConfig):Promise - { - try{ - const response =await this.axiosInstance.request(config); - - if(response.status == 204){ - // No Content - return null; - } + return {}; + } + public async getBlob(config: AxiosRequestConfig): Promise { + try { + const response = await this.axiosInstance.request(config); - return response.data; - }catch(error : unknown){ + if (response.status == 204) { + // No Content + return null; + } - if(axios.isAxiosError(error)){ - return null; - } - else{ - console.log("Some other error ?? " + error); - } - } - return {}; + return response.data; + } catch (error: unknown) { + if (axios.isAxiosError(error)) { + return null; + } else { + console.log('Some other error ?? ' + error); + } } + return {}; + } - public async queryGraphQl(query:DocumentNode,variable:object):Promise{ - - try { - - console.log("GraphQL Query:", query.loc?.source.body); // Shows full query string -console.log("Variables:", JSON.stringify(variable, null, 2)); + public async queryGraphQl( + query: DocumentNode, + variable: object + ): Promise { + try { + console.log('GraphQL Query:', query.loc?.source.body); // Shows full query string + console.log('Variables:', JSON.stringify(variable, null, 2)); - const result = await apolloClient.query({ query, variables: variable }); - console.log(result.data); - return result.data; - } catch (err) { - console.error("GraphQL request failed:", err); - throw err; - } + const result = await apolloClient.query({ + query, + variables: variable, + }); + console.log(result.data); + return result.data; + } catch (err) { + console.error('GraphQL request failed:', err); + throw err; } + } } -export default HttpClient; \ No newline at end of file +export default HttpClient; diff --git a/client/nt.webclient/vue3withtypescript/nt/src/apiService/UserApiService.ts b/client/nt.webclient/vue3withtypescript/nt/src/apiService/UserApiService.ts index d3d85e1a..19eae9e6 100644 --- a/client/nt.webclient/vue3withtypescript/nt/src/apiService/UserApiService.ts +++ b/client/nt.webclient/vue3withtypescript/nt/src/apiService/UserApiService.ts @@ -1,75 +1,114 @@ -import { IChangePasswordRequest, IChangePasswordResponse, IGetProfileImageRequest, - IRegisterUserRequest,IUpdateUserRequest, IUpdateUserResponse, IRegisterUserResponse, - IUploadProfileImageRequest, IUploadProfileImageResponse, IValidateUserRequest, - IValidateUserResponse, ISearchUsersResponse} from "../types/apirequestresponsetypes/User"; -import { ApiServiceBase } from "./ApiServiceBase"; +import { + IChangePasswordRequest, + IChangePasswordResponse, + IGetProfileImageRequest, + IRegisterUserRequest, + IUpdateUserRequest, + IUpdateUserResponse, + IRegisterUserResponse, + IUploadProfileImageRequest, + IUploadProfileImageResponse, + IValidateUserRequest, + IValidateUserResponse, + ISearchUsersResponse, + IGetProfileResponse, +} from '../types/apirequestresponsetypes/User'; +import { ApiServiceBase } from './ApiServiceBase'; class UserApiService extends ApiServiceBase { + public async validateUser( + user: IValidateUserRequest + ): Promise { + const response = await this.invoke({ + method: 'post', + url: '/api/User/ValidateUser', + data: user, + }); + console.log('Response from validation'); + console.log(response); + return response; + } - public async validateUser(user:IValidateUserRequest):Promise{ - const response = await this.invoke({method:'post', url:"/api/User/ValidateUser", data: user}); - console.log('Response from validation') - console.log(response); - return response; - } + public async registerUser( + user: IRegisterUserRequest + ): Promise { + return await this.invoke({ + method: 'post', + url: 'api/user/createuser', + data: user, + }); + } - public async registerUser(user:IRegisterUserRequest):Promise{ - - return await this.invoke({method:'post', url:"api/user/createuser", data : user}); - } + public async changePassword( + request: IChangePasswordRequest + ): Promise { + return await this.invoke({ + method: 'post', + url: '/api/user/ChangePassword', + data: request, + }); + } - public async changePassword(request:IChangePasswordRequest):Promise{ - return await this.invoke({method:'post', url:"/api/user/ChangePassword", data : request}); - } + public async updateUser( + user: IUpdateUserRequest + ): Promise { + return await this.invoke({ + method: 'post', + url: '/user/api/Users/Update', + data: user, + }); + } + public async uploadProfileImage( + request: IUploadProfileImageRequest + ): Promise { + const formData = new FormData(); + formData.append('imageKey', request.imageKey); + formData.append('file', request.file); - public async updateUser(user:IUpdateUserRequest):Promise{ - return await this.invoke({method:'post', url:"/user/api/Users/Update", data : user}); - } + return await this.invoke({ + method: 'post', + url: 'user/api/Users/uploadprofileimage', + data: formData, + headers: { 'Content-Type': 'multipart/form-data' }, + }); + } - public async uploadProfileImage(request:IUploadProfileImageRequest):Promise{ + public async getProfileImage( + request: IGetProfileImageRequest + ): Promise { + const response = await this.getBlob({ + method: 'get', + url: 'user/api/Users/getprofileimage', + params: { + userName: request.userName, + }, + responseType: 'blob', + }); - const formData = new FormData(); - formData.append('imageKey',request.imageKey); - formData.append('file', request.file); + return response; + } - return await this.invoke( - { - method:'post', - url:"user/api/Users/uploadprofileimage", - data : formData, - headers: { "Content-Type": "multipart/form-data" } - }); - } - - public async getProfileImage(request:IGetProfileImageRequest):Promise{ - - const response = await this.getBlob({ - method:'get', - 'url':'user/api/Users/getprofileimage', - params:{ - userName : request.userName - }, - responseType: 'blob' - }) - - return response; - } - - - public async searchUsers(request:string):Promise{ - console.log(request); - return await this.invoke( - { - method:'get', - url:"/api/User/searchuser", - params : - { - searchTerm:request - } - }); - } + public async getUserProfile(userName: string): Promise { + return await this.invoke({ + method: 'get', + url: '/user/api/Users/getuserprofile', + params: { + userName: userName, + }, + }); + } + public async searchUsers(request: string): Promise { + console.log(request); + return await this.invoke({ + method: 'get', + url: '/api/User/searchuser', + params: { + searchTerm: request, + }, + }); + } } -export const userApiService = new UserApiService(); \ No newline at end of file +export const userApiService = new UserApiService(); diff --git a/client/nt.webclient/vue3withtypescript/nt/src/components/private/user/AvataarCard.vue b/client/nt.webclient/vue3withtypescript/nt/src/components/private/user/AvataarCard.vue index f3f891eb..29a9ccfe 100644 --- a/client/nt.webclient/vue3withtypescript/nt/src/components/private/user/AvataarCard.vue +++ b/client/nt.webclient/vue3withtypescript/nt/src/components/private/user/AvataarCard.vue @@ -12,6 +12,7 @@ class="avataar image-center rounded-circle card-img img-thumbnail mx-auto d-block" alt="Profile Image" /> + {{ userProfile.displayName }}
(), { isReadOnly: false, showUserName: false, @@ -48,6 +54,10 @@ }); const currentUserName = ref(props.userName); + const userProfile = ref({ + userName: props.userName, + displayName: '', + }); const fileUploader = ref(null); @@ -60,6 +70,7 @@ const canExecute = ref(false); onMounted(() => { getProfileImage(); + getUserProfile(); // check if profile image exist in blob //imgSrc.value = defaultImage; }); @@ -80,6 +91,21 @@ } }; + const getUserProfile = async (): Promise => { + var response = await userApiService.getUserProfile(props.userName); + + if (response.hasError) { + // TODO : Error Handling + return; + } + + currentUserName.value = response.user.userName; + userProfile.value = { + userName: response.user.userName, + displayName: response.user.displayName ?? '', + }; + }; + const isValidBlob = (blob: Blob | null): boolean => { return blob instanceof Blob && blob.size > 0; }; diff --git a/client/nt.webclient/vue3withtypescript/nt/src/types/apirequestresponsetypes/User.ts b/client/nt.webclient/vue3withtypescript/nt/src/types/apirequestresponsetypes/User.ts index 8bb1523c..56fef09c 100644 --- a/client/nt.webclient/vue3withtypescript/nt/src/types/apirequestresponsetypes/User.ts +++ b/client/nt.webclient/vue3withtypescript/nt/src/types/apirequestresponsetypes/User.ts @@ -36,7 +36,7 @@ export interface IValidateUserResponse extends IResponseBase { userName: string; displayName?: string; bio?: string; - followers:string[]; + followers: string[]; }; } @@ -69,3 +69,7 @@ export interface IGetProfileImageResponse extends IResponseBase { export interface ISearchUsersResponse extends IResponseBase { users: User[]; } + +export interface IGetProfileResponse extends IResponseBase { + user: User; +} diff --git a/server/nt.microservice/infrastructure/nt.gateway/ocelot.json b/server/nt.microservice/infrastructure/nt.gateway/ocelot.json index e7643aec..7eed4113 100644 --- a/server/nt.microservice/infrastructure/nt.gateway/ocelot.json +++ b/server/nt.microservice/infrastructure/nt.gateway/ocelot.json @@ -108,6 +108,27 @@ } }, + // Get User Profile Name + { + "DownstreamPathTemplate": "/api/usermanagement/SearchUserByUserName/{userName}", + "DownstreamSchema": "https", + "DownstreamHttpMethod": "GET", + "RouteIsCaseSensitive": false, + "ServiceName": "nt.userservice.service", + "UseServiceDiscovery": true, + "UpstreamPathTemplate": "/user/api/Users/getuserprofile/{userName}", + "UpstreamHttpMethod": [ "GET" ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 10, + "DurationOfBreak": 10000, + "TimeoutValue": 3000 + }, + "AuthenticationOptions": { + "AuthenticationProviderKey": "GatewayAuthenticationKey", + "AllowedScopes": [] + } + }, + // Upload Profile Image { "DownstreamPathTemplate": "/api/users/uploadprofileimage", From f11ee24daff4345c5906daf35475f3edfed0736b Mon Sep 17 00:00:00 2001 From: Anu Viswan Date: Tue, 5 Aug 2025 08:52:07 +0530 Subject: [PATCH 04/19] working on displaying user display name --- .../vue3withtypescript/nt/src/apiService/HttpClient.ts | 1 + .../vue3withtypescript/nt/src/apiService/UserApiService.ts | 5 +---- .../nt/src/components/private/reviews/ReviewCard.vue | 3 ++- .../nt/src/pages/private/DashboardPage.vue | 2 +- .../nt/src/types/apirequestresponsetypes/Review.ts | 2 +- server/nt.microservice/infrastructure/nt.gateway/ocelot.json | 1 + .../UserService.Api/Controllers/UserController.cs | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/client/nt.webclient/vue3withtypescript/nt/src/apiService/HttpClient.ts b/client/nt.webclient/vue3withtypescript/nt/src/apiService/HttpClient.ts index 45442c1f..8e187714 100644 --- a/client/nt.webclient/vue3withtypescript/nt/src/apiService/HttpClient.ts +++ b/client/nt.webclient/vue3withtypescript/nt/src/apiService/HttpClient.ts @@ -58,6 +58,7 @@ class HttpClient { } public async getBlob(config: AxiosRequestConfig): Promise { try { + console.log('Sending profile image request for:', config); const response = await this.axiosInstance.request(config); if (response.status == 204) { diff --git a/client/nt.webclient/vue3withtypescript/nt/src/apiService/UserApiService.ts b/client/nt.webclient/vue3withtypescript/nt/src/apiService/UserApiService.ts index 19eae9e6..8bc4f804 100644 --- a/client/nt.webclient/vue3withtypescript/nt/src/apiService/UserApiService.ts +++ b/client/nt.webclient/vue3withtypescript/nt/src/apiService/UserApiService.ts @@ -92,10 +92,7 @@ class UserApiService extends ApiServiceBase { public async getUserProfile(userName: string): Promise { return await this.invoke({ method: 'get', - url: '/user/api/Users/getuserprofile', - params: { - userName: userName, - }, + url: `/user/api/Users/getuserprofile/${encodeURIComponent(userName)}`, }); } diff --git a/client/nt.webclient/vue3withtypescript/nt/src/components/private/reviews/ReviewCard.vue b/client/nt.webclient/vue3withtypescript/nt/src/components/private/reviews/ReviewCard.vue index e2118c4e..c8144527 100644 --- a/client/nt.webclient/vue3withtypescript/nt/src/components/private/reviews/ReviewCard.vue +++ b/client/nt.webclient/vue3withtypescript/nt/src/components/private/reviews/ReviewCard.vue @@ -47,8 +47,9 @@ style="width: 50px; height: 50px" >
diff --git a/client/nt.webclient/vue3withtypescript/nt/src/pages/private/DashboardPage.vue b/client/nt.webclient/vue3withtypescript/nt/src/pages/private/DashboardPage.vue index 96550948..77110548 100644 --- a/client/nt.webclient/vue3withtypescript/nt/src/pages/private/DashboardPage.vue +++ b/client/nt.webclient/vue3withtypescript/nt/src/pages/private/DashboardPage.vue @@ -70,7 +70,7 @@ content: review.content, movieId: review.movieId, movieTitle: 'Demo Title', - userName: review.author, + userName: review.userName, rating: review.rating, displayName: 'Demo User', })); diff --git a/client/nt.webclient/vue3withtypescript/nt/src/types/apirequestresponsetypes/Review.ts b/client/nt.webclient/vue3withtypescript/nt/src/types/apirequestresponsetypes/Review.ts index 92c00cae..7c688d3b 100644 --- a/client/nt.webclient/vue3withtypescript/nt/src/types/apirequestresponsetypes/Review.ts +++ b/client/nt.webclient/vue3withtypescript/nt/src/types/apirequestresponsetypes/Review.ts @@ -15,5 +15,5 @@ export interface IRecentReviewsForUsersResponseItem { movieId: string; content: string; rating: number; - author: string; + userName: string; } diff --git a/server/nt.microservice/infrastructure/nt.gateway/ocelot.json b/server/nt.microservice/infrastructure/nt.gateway/ocelot.json index 7eed4113..dc7a9b89 100644 --- a/server/nt.microservice/infrastructure/nt.gateway/ocelot.json +++ b/server/nt.microservice/infrastructure/nt.gateway/ocelot.json @@ -188,6 +188,7 @@ }, + // Get REcent Reviews { "DownstreamPathTemplate": "/api/UserReviews/GetRecentReviewsForUsers", "DownstreamScheme": "http", diff --git a/server/nt.microservice/services/UserService/UserService.Api/Controllers/UserController.cs b/server/nt.microservice/services/UserService/UserService.Api/Controllers/UserController.cs index 70a04b50..a16ff388 100644 --- a/server/nt.microservice/services/UserService/UserService.Api/Controllers/UserController.cs +++ b/server/nt.microservice/services/UserService/UserService.Api/Controllers/UserController.cs @@ -139,7 +139,7 @@ public async Task UpdateProfileImage([FromForm]UpdateProfileImage [Route("getprofileimage")] [Authorize] [TechnicalDebt(DebtType.BadDesign,"Exception Handling has to improve when expected return type is image/jpeg")] - public async Task GetProfileImage(string userName) + public async Task GetProfileImage([FromQuery]string userName) { try { From 3cb478fd16df8a6ff7ea00e284320567c33706cf Mon Sep 17 00:00:00 2001 From: Anu Viswan Date: Wed, 6 Aug 2025 22:07:50 +0530 Subject: [PATCH 05/19] Adding Search By MovieId --- .../Controllers/MovieController.cs | 2 ++ .../ViewModels/MovieSearchResult.cs | 1 + .../Entities/MovieEntity.cs | 2 +- .../Services/IMovieCrudService.cs | 1 + .../Services/MovieCrudService.cs | 23 +++++++++++++++++++ .../Queries/MovieQuery.cs | 21 +++++++++++++++++ .../MovieService.GraphQL/Types/MovieType.cs | 4 ++++ .../Dtos/MovieDto.cs | 1 + .../Services/IMovieService.cs | 2 ++ .../Services/MovieService.cs | 21 +++++++++++++++++ 10 files changed, 77 insertions(+), 1 deletion(-) diff --git a/server/nt.microservice/services/MovieService/MovieService.Api/Controllers/MovieController.cs b/server/nt.microservice/services/MovieService/MovieService.Api/Controllers/MovieController.cs index cc7fe05c..7cce4706 100644 --- a/server/nt.microservice/services/MovieService/MovieService.Api/Controllers/MovieController.cs +++ b/server/nt.microservice/services/MovieService/MovieService.Api/Controllers/MovieController.cs @@ -49,4 +49,6 @@ public async IAsyncEnumerable SearchMovieByName(string search yield return Mapper.Map(movie); } } + + } \ No newline at end of file diff --git a/server/nt.microservice/services/MovieService/MovieService.Api/ViewModels/MovieSearchResult.cs b/server/nt.microservice/services/MovieService/MovieService.Api/ViewModels/MovieSearchResult.cs index 505b7714..2c5b4256 100644 --- a/server/nt.microservice/services/MovieService/MovieService.Api/ViewModels/MovieSearchResult.cs +++ b/server/nt.microservice/services/MovieService/MovieService.Api/ViewModels/MovieSearchResult.cs @@ -10,3 +10,4 @@ public record MovieSearchResult public string? Director { get; set; } } + diff --git a/server/nt.microservice/services/MovieService/MovieService.Data.Interfaces/Entities/MovieEntity.cs b/server/nt.microservice/services/MovieService/MovieService.Data.Interfaces/Entities/MovieEntity.cs index a5a37c26..d1fa22c9 100644 --- a/server/nt.microservice/services/MovieService/MovieService.Data.Interfaces/Entities/MovieEntity.cs +++ b/server/nt.microservice/services/MovieService/MovieService.Data.Interfaces/Entities/MovieEntity.cs @@ -23,7 +23,7 @@ public class MovieEntity : Entity [Field("crew")] - public Dictionary>? Crew { get; set; } + public Dictionary> Crew { get; set; } = new Dictionary>(); [Field("cast")] public List Cast { get; set; } = []; diff --git a/server/nt.microservice/services/MovieService/MovieService.Data.Interfaces/Services/IMovieCrudService.cs b/server/nt.microservice/services/MovieService/MovieService.Data.Interfaces/Services/IMovieCrudService.cs index 0e687b01..8435c2d8 100644 --- a/server/nt.microservice/services/MovieService/MovieService.Data.Interfaces/Services/IMovieCrudService.cs +++ b/server/nt.microservice/services/MovieService/MovieService.Data.Interfaces/Services/IMovieCrudService.cs @@ -5,4 +5,5 @@ public interface IMovieCrudService Task CreateAsync(MovieEntity newBook); IAsyncEnumerable SearchAsync(string searchTerm); IAsyncEnumerable GetRecentMovies(int count = 10); + Task GetMovieByIdAsync(string id); } diff --git a/server/nt.microservice/services/MovieService/MovieService.Data/Services/MovieCrudService.cs b/server/nt.microservice/services/MovieService/MovieService.Data/Services/MovieCrudService.cs index d74753f0..d2ed0083 100644 --- a/server/nt.microservice/services/MovieService/MovieService.Data/Services/MovieCrudService.cs +++ b/server/nt.microservice/services/MovieService/MovieService.Data/Services/MovieCrudService.cs @@ -2,6 +2,7 @@ using MongoDB.Entities; using MovieService.Data.Interfaces.Entities; using MovieService.Data.Interfaces.Services; +using System.Net.Http.Headers; namespace MovieService.Data.Services; public class MovieCrudService : IMovieCrudService @@ -20,6 +21,18 @@ public async IAsyncEnumerable SearchAsync(string searchTerm) { var cursor = await DB.Find() .Match(Search.Full, searchTerm) + .Project(movie => new MovieEntity + { + ID = movie.ID, + Title = movie.Title, + MovieLanguage = movie.MovieLanguage, + ReleaseDate = movie.ReleaseDate, + Synopsis = movie.Synopsis, + Cast = movie.Cast.Select(c => new PersonEntity { Name = c.Name }).ToList(), + Crew = movie.Crew.ToDictionary( + kvp => kvp.Key, + kvp => kvp.Value.Select(p => new PersonEntity { Name = p.Name }).ToList()) + }) .ExecuteCursorAsync(); while (await cursor.MoveNextAsync()) @@ -47,4 +60,14 @@ public async IAsyncEnumerable GetRecentMovies(int count = 10) } } } + + public async Task GetMovieByIdAsync(string id) + { + if (string.IsNullOrEmpty(id)) + return null; + + return await DB.Find() + .Match(x => x.ID == id) + .ExecuteFirstAsync(); + } } diff --git a/server/nt.microservice/services/MovieService/MovieService.GraphQL/Queries/MovieQuery.cs b/server/nt.microservice/services/MovieService/MovieService.GraphQL/Queries/MovieQuery.cs index 0765b241..089917f4 100644 --- a/server/nt.microservice/services/MovieService/MovieService.GraphQL/Queries/MovieQuery.cs +++ b/server/nt.microservice/services/MovieService/MovieService.GraphQL/Queries/MovieQuery.cs @@ -15,6 +15,7 @@ public async IAsyncEnumerable FindMovie([GraphQLName("searchTerm")]st { yield return new MovieType { + Id = dto?.Id?.ToString() ?? Guid.Empty.ToString(), Title = dto.Title, MovieLanguage = dto.MovieLanguage ?? "Unknown", ReleaseDate = dto.ReleaseDate ?? DateTime.MinValue, @@ -46,6 +47,26 @@ public async IAsyncEnumerable GetRecentMovies([GraphQLName("count")]i }; } } + + public async Task GetMovieById([GraphQLName("id")] string id) + { + var dto = await movieService.GetMovieById(id); + if (dto == null) + { + throw new GraphQLException(new Error("Movie not found", "MOVIE_NOT_FOUND")); + } + return new MovieType + { + Title = dto.Title, + MovieLanguage = dto.MovieLanguage ?? "Unknown", + ReleaseDate = dto.ReleaseDate ?? DateTime.MinValue, + Synopsis = "Synopsis not provided", // Assuming no synopsis in DTO + Cast = dto.Cast?.Select(x=>new PersonType { Name = x.Name}).ToList() ?? [], + Crew = dto.Crew?.ToDictionary( + kvp => kvp.Key, + kvp => kvp.Value.Select(p => new PersonType { Name = p.Name }).ToList()) ?? [] + }; + } } diff --git a/server/nt.microservice/services/MovieService/MovieService.GraphQL/Types/MovieType.cs b/server/nt.microservice/services/MovieService/MovieService.GraphQL/Types/MovieType.cs index ef15843a..b29fdf74 100644 --- a/server/nt.microservice/services/MovieService/MovieService.GraphQL/Types/MovieType.cs +++ b/server/nt.microservice/services/MovieService/MovieService.GraphQL/Types/MovieType.cs @@ -4,6 +4,10 @@ namespace MovieService.GraphQL.Types; public class MovieType { + [GraphQLName("id")] + [GraphQLDescription("Unique identifier for the movie.")] + public string Id { get; set; } = null!; + [GraphQLName("title")] [GraphQLDescription("Title of movie.")] public string Title { get; set; } = null!; diff --git a/server/nt.microservice/services/MovieService/MovieService.Service.Interfaces/Dtos/MovieDto.cs b/server/nt.microservice/services/MovieService/MovieService.Service.Interfaces/Dtos/MovieDto.cs index 0e15f7d5..52f1a190 100644 --- a/server/nt.microservice/services/MovieService/MovieService.Service.Interfaces/Dtos/MovieDto.cs +++ b/server/nt.microservice/services/MovieService/MovieService.Service.Interfaces/Dtos/MovieDto.cs @@ -2,6 +2,7 @@ public record MovieDto { + public string Id { get; set; } = null!; public string Title { get; set; } = null!; public string Synopsis { get; set; } = string.Empty; diff --git a/server/nt.microservice/services/MovieService/MovieService.Service.Interfaces/Services/IMovieService.cs b/server/nt.microservice/services/MovieService/MovieService.Service.Interfaces/Services/IMovieService.cs index f7d4f2ba..ec2650c8 100644 --- a/server/nt.microservice/services/MovieService/MovieService.Service.Interfaces/Services/IMovieService.cs +++ b/server/nt.microservice/services/MovieService/MovieService.Service.Interfaces/Services/IMovieService.cs @@ -7,4 +7,6 @@ public interface IMovieService Task CreateMovie(MovieDto movie); IAsyncEnumerable Search(string searchTerm); IAsyncEnumerable GetRecentMovies(int count = 10); + + Task GetMovieById(string id); } diff --git a/server/nt.microservice/services/MovieService/MovieService.Service/Services/MovieService.cs b/server/nt.microservice/services/MovieService/MovieService.Service/Services/MovieService.cs index 7cbf48b7..338c89a5 100644 --- a/server/nt.microservice/services/MovieService/MovieService.Service/Services/MovieService.cs +++ b/server/nt.microservice/services/MovieService/MovieService.Service/Services/MovieService.cs @@ -42,6 +42,8 @@ public async Task CreateMovie(MovieDto movie) } + + public async IAsyncEnumerable GetRecentMovies(int count = 10) { if (count <= 0) @@ -64,4 +66,23 @@ public async IAsyncEnumerable Search(string searchTerm) yield return Mapper.Map(movie); } } + + public async Task GetMovieById(string id) + { + try + { + var movie = await _movieCrudService.GetMovieByIdAsync(id).ConfigureAwait(false); + if(movie is not null) + { + return Mapper.Map(movie); + } + + return null; + } + catch (Exception ex) + { + Logger.LogError(ex, "Unable to find movie with {Id}", id); + throw; + } + } } From 4375cc929496271b8d3938d58bde6440395fd37e Mon Sep 17 00:00:00 2001 From: Anu Viswan Date: Thu, 7 Aug 2025 07:28:25 +0530 Subject: [PATCH 06/19] fixed id fetch issue --- .../MovieService/MovieService.Api/Helpers/Mapper.cs | 2 ++ .../MovieService.Data/Services/MovieCrudService.cs | 13 +------------ 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/server/nt.microservice/services/MovieService/MovieService.Api/Helpers/Mapper.cs b/server/nt.microservice/services/MovieService/MovieService.Api/Helpers/Mapper.cs index d1af297a..40827994 100644 --- a/server/nt.microservice/services/MovieService/MovieService.Api/Helpers/Mapper.cs +++ b/server/nt.microservice/services/MovieService/MovieService.Api/Helpers/Mapper.cs @@ -18,6 +18,8 @@ public static void RegisterTypes() // Use InjectFrom to copy the base properties dto.InjectFrom(src); + dto.Id = src.ID; // Ensure the ID is set correctly from the Entity + // Map the CastAndCrew dictionary if (src.Crew?.Any() == true) { diff --git a/server/nt.microservice/services/MovieService/MovieService.Data/Services/MovieCrudService.cs b/server/nt.microservice/services/MovieService/MovieService.Data/Services/MovieCrudService.cs index d2ed0083..621274c2 100644 --- a/server/nt.microservice/services/MovieService/MovieService.Data/Services/MovieCrudService.cs +++ b/server/nt.microservice/services/MovieService/MovieService.Data/Services/MovieCrudService.cs @@ -21,18 +21,7 @@ public async IAsyncEnumerable SearchAsync(string searchTerm) { var cursor = await DB.Find() .Match(Search.Full, searchTerm) - .Project(movie => new MovieEntity - { - ID = movie.ID, - Title = movie.Title, - MovieLanguage = movie.MovieLanguage, - ReleaseDate = movie.ReleaseDate, - Synopsis = movie.Synopsis, - Cast = movie.Cast.Select(c => new PersonEntity { Name = c.Name }).ToList(), - Crew = movie.Crew.ToDictionary( - kvp => kvp.Key, - kvp => kvp.Value.Select(p => new PersonEntity { Name = p.Name }).ToList()) - }) + //.Project(movie =>movie) .ExecuteCursorAsync(); while (await cursor.MoveNextAsync()) From c330606dda6744749930f2129f053b29c2f64db3 Mon Sep 17 00:00:00 2001 From: Anu Viswan Date: Thu, 7 Aug 2025 08:31:46 +0530 Subject: [PATCH 07/19] Fixed ID in get movie by id --- .../MovieService/MovieService.GraphQL/Queries/MovieQuery.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/nt.microservice/services/MovieService/MovieService.GraphQL/Queries/MovieQuery.cs b/server/nt.microservice/services/MovieService/MovieService.GraphQL/Queries/MovieQuery.cs index 089917f4..c0f5bf02 100644 --- a/server/nt.microservice/services/MovieService/MovieService.GraphQL/Queries/MovieQuery.cs +++ b/server/nt.microservice/services/MovieService/MovieService.GraphQL/Queries/MovieQuery.cs @@ -36,6 +36,7 @@ public async IAsyncEnumerable GetRecentMovies([GraphQLName("count")]i { yield return new MovieType { + Id = dto?.Id?.ToString() ?? Guid.Empty.ToString(), Title = dto.Title, MovieLanguage = dto.MovieLanguage ?? "Unknown", ReleaseDate = dto.ReleaseDate ?? DateTime.MinValue, @@ -57,6 +58,7 @@ public async Task GetMovieById([GraphQLName("id")] string id) } return new MovieType { + Id = dto?.Id?.ToString() ?? Guid.Empty.ToString(), Title = dto.Title, MovieLanguage = dto.MovieLanguage ?? "Unknown", ReleaseDate = dto.ReleaseDate ?? DateTime.MinValue, From 63aafbc728500742d955612d20334aff4dc7b659 Mon Sep 17 00:00:00 2001 From: Anu Viswan Date: Thu, 7 Aug 2025 22:19:02 +0530 Subject: [PATCH 08/19] remote extra template ReviewCard --- .../components/private/reviews/ReviewCard.vue | 12 +-- .../nt/src/pages/private/DashboardPage.vue | 5 +- server/Configuration/mockoon-env.json | 80 ++++++++++++++++++- 3 files changed, 88 insertions(+), 9 deletions(-) diff --git a/client/nt.webclient/vue3withtypescript/nt/src/components/private/reviews/ReviewCard.vue b/client/nt.webclient/vue3withtypescript/nt/src/components/private/reviews/ReviewCard.vue index c8144527..fc29b80d 100644 --- a/client/nt.webclient/vue3withtypescript/nt/src/components/private/reviews/ReviewCard.vue +++ b/client/nt.webclient/vue3withtypescript/nt/src/components/private/reviews/ReviewCard.vue @@ -1,7 +1,8 @@ -