Parcourir la source

Allow deleting of tracks

Lukas Angerer il y a 3 ans
Parent
commit
99eb8fb278

+ 4 - 0
src/RunnersMeet.Client/src/app/tracks-api.service.ts

@@ -28,4 +28,8 @@ export class TracksApiService {
 	public updateTrack(track: Track): Promise<Track> {
 		return lastValueFrom(this.http.put<Track>(`${environment.apiUri}/api/tracks/${track.trackId}`, track));
 	}
+
+	public deleteTrack(track: Track): Promise<void> {
+		return lastValueFrom(this.http.delete(`${environment.apiUri}/api/tracks/${track.trackId}`)).then();
+	}
 }

+ 1 - 1
src/RunnersMeet.Client/src/app/tracks/track-edit/track-edit.component.html

@@ -21,6 +21,6 @@
 		<label>Elevation down (m):</label> {{ track.elevationDown }}
 	</div>
 	<div>
-		<button type="submit">Save</button>
+		<button type="submit">Save</button> <button type="button" (click)="deleteTrack()">Delete</button>
 	</div>
 </form>

+ 6 - 0
src/RunnersMeet.Client/src/app/tracks/track-edit/track-edit.component.ts

@@ -30,4 +30,10 @@ export class TrackEditComponent implements OnInit {
 			this.track = result;
 		});
 	}
+
+	public deleteTrack(): void {
+		this.tracksApi.deleteTrack(this.track).then(() => {
+			this.router.navigateByUrl("/tracks");
+		});
+	}
 }

+ 13 - 3
src/RunnersMeet.Server/Controllers/TracksController.cs

@@ -36,7 +36,7 @@ public class TracksController : ControllerBase
 		var fileName = await _fileStorage.UploadFileAsync(file, cancellationToken);
 
 		var gpxSummary = _gpxParser.ExtractSummary(_fileStorage.OpenFileRead(fileName));
-		var userId = HttpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "<unknown>";
+		var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "<unknown>";
 		var track = _queryFactory.CreateTrackCommand().Create(userId, fileName, gpxSummary);
 
 		return Ok(track);
@@ -51,15 +51,25 @@ public class TracksController : ControllerBase
 	}
 
 	[HttpPut("{id}")]
-	public ActionResult<Track> UpdateTrack([FromRoute] string id, [FromBody] Track track)
+	public ActionResult<Track> UpdateTrack(string id, [FromBody] Track track)
 	{
 		if (id != track.TrackId.ToString())
 		{
 			throw new ArgumentException("Object ID in URL does not match track ID");
 		}
-		var userId = HttpContext.User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "<unknown>";
+		var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "<unknown>";
 		var result = _queryFactory.UpdateTrackCommand().Update(userId, track);
 
 		return Ok(result);
 	}
+
+	[HttpDelete("{id}")]
+	public ActionResult DeleteTrack(string id)
+	{
+		var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "<unknown>";
+		var fileName = _queryFactory.DeleteTrackCommand().Delete(userId, new ObjectId(id));
+		_fileStorage.DeleteFile(fileName);
+
+		return Ok();
+	}
 }

+ 1 - 0
src/RunnersMeet.Server/GpxFormat/GpxParser.cs

@@ -8,6 +8,7 @@ public class GpxParser
 	{
 		var doc = new XmlDocument();
 		doc.Load(gpxStream);
+		gpxStream.Dispose();
 
 		return new GpxSummary(doc);
 	}

+ 32 - 0
src/RunnersMeet.Server/Persistence/DeleteTrackCommand.cs

@@ -0,0 +1,32 @@
+using System.Security;
+using LiteDB;
+
+namespace RunnersMeet.Server.Persistence;
+
+public class DeleteTrackCommand
+{
+	private readonly IDatabase _database;
+
+	public DeleteTrackCommand(IDatabase database)
+	{
+		_database = database;
+	}
+
+	public FileName Delete(string owner, ObjectId trackId)
+	{
+		var dbTrack = _database.Tracks.FindById(trackId);
+
+		if (dbTrack == null)
+		{
+			throw new ArgumentException($"Track with OID {trackId} does not exist", nameof(trackId));
+		}
+
+		if (dbTrack.Owner != owner)
+		{
+			throw new SecurityException("Trying to delete a track owned by somebody else");
+		}
+
+		_database.Tracks.Delete(trackId);
+		return FileName.FromTrack(dbTrack);
+	}
+}

+ 6 - 0
src/RunnersMeet.Server/Persistence/FileName.cs

@@ -1,4 +1,5 @@
 using System.Security.Cryptography;
+using RunnersMeet.Server.Domain;
 
 namespace RunnersMeet.Server.Persistence;
 
@@ -25,4 +26,9 @@ public class FileName
 
 		return new FileName(hash, file.FileName);
 	}
+
+	public static FileName FromTrack(Track track)
+	{
+		return new FileName(track.FileHash, $"{track.DisplayName}.gpx");
+	}
 }

+ 6 - 0
src/RunnersMeet.Server/Persistence/FileStorage.cs

@@ -36,4 +36,10 @@ public class FileStorage : IFileStorage
 
 		return File.Create(path);
 	}
+
+	public void DeleteFile(FileName name)
+	{
+		var path = Path.Combine(_persistenceOptions.FileStorageRootPath, name.GetPath());
+		File.Delete(path);
+	}
 }

+ 1 - 0
src/RunnersMeet.Server/Persistence/IFileStorage.cs

@@ -4,4 +4,5 @@ public interface IFileStorage
 {
 	public Task<FileName> UploadFileAsync(IFormFile file, CancellationToken cancellationToken = default);
 	public Stream OpenFileRead(FileName name);
+	public void DeleteFile(FileName name);
 }

+ 5 - 0
src/RunnersMeet.Server/Persistence/QueryFactory.cs

@@ -28,4 +28,9 @@ public class QueryFactory
 	{
 		return new UpdateTrackCommand(_database);
 	}
+
+	public DeleteTrackCommand DeleteTrackCommand()
+	{
+		return new DeleteTrackCommand(_database);
+	}
 }