Skip to content

Commit

Permalink
End of section 11
Browse files Browse the repository at this point in the history
  • Loading branch information
TryCatchLearn committed Aug 5, 2023
1 parent 648a270 commit d7b2fea
Show file tree
Hide file tree
Showing 26 changed files with 385 additions and 13 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -475,3 +475,4 @@ $RECYCLE.BIN/

# Windows shortcuts
*.lnk
API/appsettings.json
1 change: 1 addition & 0 deletions API/API.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

<ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="CloudinaryDotNet" Version="1.22.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.9">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
4 changes: 3 additions & 1 deletion API/Controllers/AccountController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public async Task<ActionResult<UserDto>> Register(RegisterDto registerDto)
public async Task<ActionResult<UserDto>> Login(LoginDto loginDto)
{
var user = await _context.Users
.Include(p => p.Photos)
.SingleOrDefaultAsync(x => x.UserName == loginDto.Username);

if (user == null) return Unauthorized("Invalid username");
Expand All @@ -64,7 +65,8 @@ public async Task<ActionResult<UserDto>> Login(LoginDto loginDto)
return new UserDto
{
Username = user.UserName,
Token = _tokenService.CreateToken(user)
Token = _tokenService.CreateToken(user),
PhotoUrl = user.Photos.FirstOrDefault(x => x.IsMain)?.Url
};
}

Expand Down
78 changes: 75 additions & 3 deletions API/Controllers/UsersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using API.Data;
using API.DTOs;
using API.Entities;
using API.Extensions;
using API.Interfaces;
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
Expand All @@ -15,9 +16,11 @@ public class UsersController : BaseApiController
{
private readonly IUserRepository _userRepository;
private readonly IMapper _mapper;
private readonly IPhotoService _photoService;

public UsersController(IUserRepository userRepository, IMapper mapper)
public UsersController(IUserRepository userRepository, IMapper mapper, IPhotoService photoService)
{
_photoService = photoService;
_mapper = mapper;
_userRepository = userRepository;
}
Expand All @@ -39,8 +42,7 @@ public async Task<ActionResult<MemberDto>> GetUser(string username)
[HttpPut]
public async Task<ActionResult> UpdateUser(MemberUpdateDto memberUpdateDto)
{
var username = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var user = await _userRepository.GetUserByUsernameAsync(username);
var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername());

_mapper.Map(memberUpdateDto, user);

Expand All @@ -50,4 +52,74 @@ public async Task<ActionResult> UpdateUser(MemberUpdateDto memberUpdateDto)

return BadRequest("Failed to update user");
}

[HttpPost("add-photo")]
public async Task<ActionResult<PhotoDto>> AddPhoto(IFormFile file)
{
var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername());

var result = await _photoService.AddPhotoAsync(file);

if (result.Error != null) return BadRequest(result.Error.Message);

var photo = new Photo
{
Url = result.SecureUrl.AbsoluteUri,
PublicId = result.PublicId
};

if (user.Photos.Count == 0) photo.IsMain = true;

user.Photos.Add(photo);

if (await _userRepository.SaveAllAsync())
return CreatedAtAction(nameof(GetUser), new { username = user.UserName },
_mapper.Map<PhotoDto>(photo));

return BadRequest("Problem adding photo");
}

[HttpPut("set-main-photo/{photoId}")]
public async Task<ActionResult> SetMainPhoto(int photoId)
{
var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername());

var photo = user.Photos.FirstOrDefault(x => x.Id == photoId);

if (photo == null) return NotFound();

if (photo.IsMain) return BadRequest("This is already your main photo");

var currentMain = user.Photos.FirstOrDefault(x => x.IsMain);
if (currentMain != null) currentMain.IsMain = false;
photo.IsMain = true;

if (await _userRepository.SaveAllAsync()) return NoContent();

return BadRequest("Problem setting main photo");
}

[HttpDelete("delete-photo/{photoId}")]
public async Task<ActionResult> DeletePhoto(int photoId)
{
var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername());

var photo = user.Photos.FirstOrDefault(x => x.Id == photoId);

if (photo == null) return NotFound();

if (photo.IsMain) return BadRequest("You cannot delete your main photo");

if (photo.PublicId != null)
{
var result = await _photoService.DeletePhotoAsync(photo.PublicId);
if (result.Error != null) return BadRequest(result.Error.Message);
}

user.Photos.Remove(photo);

if (await _userRepository.SaveAllAsync()) return Ok();

return BadRequest("Problem deleting photo");
}
}
1 change: 1 addition & 0 deletions API/DTOs/UserDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ public class UserDto
{
public string Username { get; set; }
public string Token { get; set; }
public string PhotoUrl { get; set; }
}
3 changes: 3 additions & 0 deletions API/Extensions/ApplicationServiceExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using API.Data;
using API.Helpers;
using API.Interfaces;
using API.Services;
using Microsoft.EntityFrameworkCore;
Expand All @@ -16,6 +17,8 @@ public static IServiceCollection AddApplicationServices(this IServiceCollection
});
services.AddScoped<IUserRepository, UserRepository>();
services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
services.Configure<CloudinarySettings>(config.GetSection("CloudinarySettings"));
services.AddScoped<IPhotoService, PhotoService>();

return services;
}
Expand Down
11 changes: 11 additions & 0 deletions API/Extensions/ClaimsPrincipalExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Security.Claims;

namespace API.Extensions;

public static class ClaimsPrincipalExtensions
{
public static string GetUsername(this ClaimsPrincipal user)
{
return user.FindFirst(ClaimTypes.NameIdentifier)?.Value;
}
}
8 changes: 8 additions & 0 deletions API/Helpers/CloudinarySettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace API.Helpers;

public class CloudinarySettings
{
public string CloudName { get; set; }
public string ApiKey { get; set; }
public string ApiSecret { get; set; }
}
9 changes: 9 additions & 0 deletions API/Interfaces/IPhotoService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using CloudinaryDotNet.Actions;

namespace API.Interfaces;

public interface IPhotoService
{
Task<ImageUploadResult> AddPhotoAsync(IFormFile file);
Task<DeletionResult> DeletePhotoAsync(string publicId);
}
49 changes: 49 additions & 0 deletions API/Services/PhotoService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using API.Helpers;
using API.Interfaces;
using CloudinaryDotNet;
using CloudinaryDotNet.Actions;
using Microsoft.Extensions.Options;

namespace API.Services;

public class PhotoService : IPhotoService
{
private readonly Cloudinary _cloudinary;
public PhotoService(IOptions<CloudinarySettings> config)
{
var acc = new Account
(
config.Value.CloudName,
config.Value.ApiKey,
config.Value.ApiSecret
);

_cloudinary = new Cloudinary(acc);
}

public async Task<ImageUploadResult> AddPhotoAsync(IFormFile file)
{
var uploadResult = new ImageUploadResult();

if (file.Length > 0)
{
using var stream = file.OpenReadStream();
var uploadParams = new ImageUploadParams
{
File = new FileDescription(file.FileName, stream),
Transformation = new Transformation().Height(500).Width(500).Crop("fill").Gravity("face"),
Folder = "da-net7u"
};
uploadResult = await _cloudinary.UploadAsync(uploadParams);
}

return uploadResult;
}

public async Task<DeletionResult> DeletePhotoAsync(string publicId)
{
var deleteParams = new DeletionParams(publicId);

return await _cloudinary.DestroyAsync(deleteParams);
}
}
Binary file modified API/datingapp.db
Binary file not shown.
Binary file modified API/datingapp.db-shm
Binary file not shown.
Binary file modified API/datingapp.db-wal
Binary file not shown.
13 changes: 13 additions & 0 deletions client/package-lock.json

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

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"bootswatch": "^5.3.1",
"font-awesome": "^4.7.0",
"ng-gallery": "^11.0.0",
"ng2-file-upload": "^5.0.0",
"ngx-bootstrap": "^11.0.2",
"ngx-spinner": "^16.0.2",
"ngx-toastr": "^17.0.2",
Expand Down
1 change: 1 addition & 0 deletions client/src/app/_models/user.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface User {
username: string;
token: string;
photoUrl: string;
}
7 changes: 5 additions & 2 deletions client/src/app/_modules/shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { TabsModule } from 'ngx-bootstrap/tabs';
import { ToastrModule } from 'ngx-toastr';
import { NgxSpinnerModule } from 'ngx-spinner';
import { FileUploadModule } from 'ng2-file-upload';

@NgModule({
declarations: [],
Expand All @@ -16,13 +17,15 @@ import { NgxSpinnerModule } from 'ngx-spinner';
}),
NgxSpinnerModule.forRoot({
type: 'line-scale-party'
})
}),
FileUploadModule
],
exports: [
BsDropdownModule,
ToastrModule,
TabsModule,
NgxSpinnerModule
NgxSpinnerModule,
FileUploadModule
]
})
export class SharedModule { }
7 changes: 3 additions & 4 deletions client/src/app/_services/account.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ export class AccountService {
map((response: User) => {
const user = response;
if (user) {
localStorage.setItem('user', JSON.stringify(user));
this.currentUserSource.next(user);
this.setCurrentUser(user);
}
})
)
Expand All @@ -31,14 +30,14 @@ export class AccountService {
map(response => {
const user = response;
if (user) {
localStorage.setItem('user', JSON.stringify(user));
this.currentUserSource.next(user);
this.setCurrentUser(user);
}
})
)
}

setCurrentUser(user: User) {
localStorage.setItem('user', JSON.stringify(user));
this.currentUserSource.next(user);
}

Expand Down
8 changes: 8 additions & 0 deletions client/src/app/_services/members.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,12 @@ export class MembersService {
})
)
}

setMainPhoto(photoId: number) {
return this.http.put(this.baseUrl + 'users/set-main-photo/' + photoId, {});
}

deletePhoto(photoId: number) {
return this.http.delete(this.baseUrl + 'users/delete-photo/' + photoId);
}
}
4 changes: 3 additions & 1 deletion client/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { MemberCardComponent } from './members/member-card/member-card.component
import { JwtInterceptor } from './_interceptors/jwt.interceptor';
import { MemberEditComponent } from './members/member-edit/member-edit.component';
import { LoadingInterceptor } from './_interceptors/loading.interceptor';
import { PhotoEditorComponent } from './members/photo-editor/photo-editor.component';

@NgModule({
declarations: [
Expand All @@ -36,7 +37,8 @@ import { LoadingInterceptor } from './_interceptors/loading.interceptor';
NotFoundComponent,
ServerErrorComponent,
MemberCardComponent,
MemberEditComponent
MemberEditComponent,
PhotoEditorComponent
],
imports: [
BrowserModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ <h4 class="mt-2">Location Details: </h4>

</tab>
<tab heading="Edit Photos">
<p>Photo edit will go here</p>
<app-photo-editor [member]="member"></app-photo-editor>
</tab>
</tabset>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.nv-file-over {
border: dotted 3px red;
}

input[type=file] {
color: transparent;
}
Loading

0 comments on commit d7b2fea

Please sign in to comment.