aboutsummaryrefslogtreecommitdiff
path: root/Timeline
diff options
context:
space:
mode:
Diffstat (limited to 'Timeline')
-rw-r--r--Timeline/Controllers/TimelineController.cs26
-rw-r--r--Timeline/Controllers/UserAvatarController.cs6
-rw-r--r--Timeline/Entities/TimelineEntity.cs3
-rw-r--r--Timeline/Filters/Timeline.cs13
-rw-r--r--Timeline/Migrations/20200826164553_TimelineAddTitle.Designer.cs341
-rw-r--r--Timeline/Migrations/20200826164553_TimelineAddTitle.cs22
-rw-r--r--Timeline/Migrations/DatabaseContextModelSnapshot.cs6
-rw-r--r--Timeline/Models/Http/Timeline.cs11
-rw-r--r--Timeline/Models/Http/TimelineController.cs24
-rw-r--r--Timeline/Models/Timeline.cs6
-rw-r--r--Timeline/Services/TimelineService.cs62
-rw-r--r--Timeline/Services/UserAvatarService.cs14
12 files changed, 520 insertions, 14 deletions
diff --git a/Timeline/Controllers/TimelineController.cs b/Timeline/Controllers/TimelineController.cs
index 90b50bbb..9a3147ea 100644
--- a/Timeline/Controllers/TimelineController.cs
+++ b/Timeline/Controllers/TimelineController.cs
@@ -308,6 +308,7 @@ namespace Timeline.Controllers
[HttpDelete("timelines/{name}/posts/{id}")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult<CommonDeleteResponse>> PostDelete([FromRoute][GeneralTimelineName] string name, [FromRoute] long id)
@@ -336,6 +337,7 @@ namespace Timeline.Controllers
[HttpPatch("timelines/{name}")]
[Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult<TimelineInfo>> TimelinePatch([FromRoute][GeneralTimelineName] string name, [FromBody] TimelinePatchRequest body)
@@ -461,5 +463,29 @@ namespace Timeline.Controllers
return CommonDeleteResponse.NotExist();
}
}
+
+ [HttpPost("timelineop/changename")]
+ [Authorize]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(StatusCodes.Status403Forbidden)]
+ public async Task<ActionResult<TimelineInfo>> TimelineOpChangeName([FromBody] TimelineChangeNameRequest body)
+ {
+ if (!this.IsAdministrator() && !(await _service.HasManagePermission(body.OldName, this.GetUserId())))
+ {
+ return StatusCode(StatusCodes.Status403Forbidden, ErrorResponse.Common.Forbid());
+ }
+
+ try
+ {
+ var timeline = await _service.ChangeTimelineName(body.OldName, body.NewName);
+ return Ok(_mapper.Map<TimelineInfo>(timeline));
+ }
+ catch (EntityAlreadyExistException)
+ {
+ return BadRequest(ErrorResponse.TimelineController.NameConflict());
+ }
+ }
}
}
diff --git a/Timeline/Controllers/UserAvatarController.cs b/Timeline/Controllers/UserAvatarController.cs
index 97c4bdb8..bc4afa30 100644
--- a/Timeline/Controllers/UserAvatarController.cs
+++ b/Timeline/Controllers/UserAvatarController.cs
@@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
using System;
using System.Threading.Tasks;
using Timeline.Auth;
@@ -105,7 +106,7 @@ namespace Timeline.Controllers
try
{
- await _service.SetAvatar(id, new Avatar
+ var etag = await _service.SetAvatar(id, new Avatar
{
Data = body.Data,
Type = body.ContentType
@@ -113,6 +114,9 @@ namespace Timeline.Controllers
_logger.LogInformation(Log.Format(LogPutSuccess,
("Username", username), ("Mime Type", Request.ContentType)));
+
+ Response.Headers.Append("ETag", new EntityTagHeaderValue($"\"{etag}\"").ToString());
+
return Ok();
}
catch (ImageException e)
diff --git a/Timeline/Entities/TimelineEntity.cs b/Timeline/Entities/TimelineEntity.cs
index b084a572..3e592673 100644
--- a/Timeline/Entities/TimelineEntity.cs
+++ b/Timeline/Entities/TimelineEntity.cs
@@ -23,6 +23,9 @@ namespace Timeline.Entities
[Column("name")]
public string? Name { get; set; }
+ [Column("title")]
+ public string? Title { get; set; }
+
[Column("name_last_modified")]
public DateTime NameLastModified { get; set; }
diff --git a/Timeline/Filters/Timeline.cs b/Timeline/Filters/Timeline.cs
index 90b87223..6a730ee7 100644
--- a/Timeline/Filters/Timeline.cs
+++ b/Timeline/Filters/Timeline.cs
@@ -1,4 +1,5 @@
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Timeline.Models.Http;
using Timeline.Services.Exceptions;
@@ -13,11 +14,17 @@ namespace Timeline.Filters
{
if (e.InnerException is UserNotExistException)
{
- context.Result = new NotFoundObjectResult(ErrorResponse.UserCommon.NotExist());
+ if (HttpMethods.IsGet(context.HttpContext.Request.Method))
+ context.Result = new NotFoundObjectResult(ErrorResponse.UserCommon.NotExist());
+ else
+ context.Result = new BadRequestObjectResult(ErrorResponse.UserCommon.NotExist());
}
else
{
- context.Result = new NotFoundObjectResult(ErrorResponse.TimelineController.NotExist());
+ if (HttpMethods.IsGet(context.HttpContext.Request.Method))
+ context.Result = new NotFoundObjectResult(ErrorResponse.TimelineController.NotExist());
+ else
+ context.Result = new BadRequestObjectResult(ErrorResponse.TimelineController.NotExist());
}
}
}
diff --git a/Timeline/Migrations/20200826164553_TimelineAddTitle.Designer.cs b/Timeline/Migrations/20200826164553_TimelineAddTitle.Designer.cs
new file mode 100644
index 00000000..f2279f3b
--- /dev/null
+++ b/Timeline/Migrations/20200826164553_TimelineAddTitle.Designer.cs
@@ -0,0 +1,341 @@
+// <auto-generated />
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Timeline.Entities;
+
+namespace Timeline.Migrations
+{
+ [DbContext(typeof(DatabaseContext))]
+ [Migration("20200826164553_TimelineAddTitle")]
+ partial class TimelineAddTitle
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "3.1.7");
+
+ modelBuilder.Entity("Timeline.Entities.DataEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("id")
+ .HasColumnType("INTEGER");
+
+ b.Property<byte[]>("Data")
+ .IsRequired()
+ .HasColumnName("data")
+ .HasColumnType("BLOB");
+
+ b.Property<int>("Ref")
+ .HasColumnName("ref")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("Tag")
+ .IsRequired()
+ .HasColumnName("tag")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Tag")
+ .IsUnique();
+
+ b.ToTable("data");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.JwtTokenEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("id")
+ .HasColumnType("INTEGER");
+
+ b.Property<byte[]>("Key")
+ .IsRequired()
+ .HasColumnName("key")
+ .HasColumnType("BLOB");
+
+ b.HasKey("Id");
+
+ b.ToTable("jwt_token");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("id")
+ .HasColumnType("INTEGER");
+
+ b.Property<DateTime>("CreateTime")
+ .HasColumnName("create_time")
+ .HasColumnType("TEXT");
+
+ b.Property<long>("CurrentPostLocalId")
+ .HasColumnName("current_post_local_id")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("Description")
+ .HasColumnName("description")
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime>("LastModified")
+ .HasColumnName("last_modified")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Name")
+ .HasColumnName("name")
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime>("NameLastModified")
+ .HasColumnName("name_last_modified")
+ .HasColumnType("TEXT");
+
+ b.Property<long>("OwnerId")
+ .HasColumnName("owner")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("Title")
+ .HasColumnName("title")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("UniqueId")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnName("unique_id")
+ .HasColumnType("TEXT")
+ .HasDefaultValueSql("lower(hex(randomblob(16)))");
+
+ b.Property<int>("Visibility")
+ .HasColumnName("visibility")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("OwnerId");
+
+ b.ToTable("timelines");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("id")
+ .HasColumnType("INTEGER");
+
+ b.Property<long>("TimelineId")
+ .HasColumnName("timeline")
+ .HasColumnType("INTEGER");
+
+ b.Property<long>("UserId")
+ .HasColumnName("user")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("TimelineId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("timeline_members");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("id")
+ .HasColumnType("INTEGER");
+
+ b.Property<long?>("AuthorId")
+ .HasColumnName("author")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("Content")
+ .HasColumnName("content")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("ContentType")
+ .IsRequired()
+ .HasColumnName("content_type")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("ExtraContent")
+ .HasColumnName("extra_content")
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime>("LastUpdated")
+ .HasColumnName("last_updated")
+ .HasColumnType("TEXT");
+
+ b.Property<long>("LocalId")
+ .HasColumnName("local_id")
+ .HasColumnType("INTEGER");
+
+ b.Property<DateTime>("Time")
+ .HasColumnName("time")
+ .HasColumnType("TEXT");
+
+ b.Property<long>("TimelineId")
+ .HasColumnName("timeline")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AuthorId");
+
+ b.HasIndex("TimelineId");
+
+ b.ToTable("timeline_posts");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("id")
+ .HasColumnType("INTEGER");
+
+ b.Property<string>("DataTag")
+ .HasColumnName("data_tag")
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime>("LastModified")
+ .HasColumnName("last_modified")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Type")
+ .HasColumnName("type")
+ .HasColumnType("TEXT");
+
+ b.Property<long>("UserId")
+ .HasColumnName("user")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("user_avatars");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserEntity", b =>
+ {
+ b.Property<long>("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("id")
+ .HasColumnType("INTEGER");
+
+ b.Property<DateTime>("CreateTime")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("create_time")
+ .HasColumnType("TEXT")
+ .HasDefaultValueSql("datetime('now', 'utc')");
+
+ b.Property<DateTime>("LastModified")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("last_modified")
+ .HasColumnType("TEXT")
+ .HasDefaultValueSql("datetime('now', 'utc')");
+
+ b.Property<string>("Nickname")
+ .HasColumnName("nickname")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Password")
+ .IsRequired()
+ .HasColumnName("password")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("Roles")
+ .IsRequired()
+ .HasColumnName("roles")
+ .HasColumnType("TEXT");
+
+ b.Property<string>("UniqueId")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnName("unique_id")
+ .HasColumnType("TEXT")
+ .HasDefaultValueSql("lower(hex(randomblob(16)))");
+
+ b.Property<string>("Username")
+ .IsRequired()
+ .HasColumnName("username")
+ .HasColumnType("TEXT");
+
+ b.Property<DateTime>("UsernameChangeTime")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("username_change_time")
+ .HasColumnType("TEXT")
+ .HasDefaultValueSql("datetime('now', 'utc')");
+
+ b.Property<long>("Version")
+ .ValueGeneratedOnAdd()
+ .HasColumnName("version")
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0L);
+
+ b.HasKey("Id");
+
+ b.HasIndex("Username")
+ .IsUnique();
+
+ b.ToTable("users");
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "Owner")
+ .WithMany("Timelines")
+ .HasForeignKey("OwnerId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelineMemberEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany("Members")
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithMany("TimelinesJoined")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Timeline.Entities.TimelinePostEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "Author")
+ .WithMany("TimelinePosts")
+ .HasForeignKey("AuthorId");
+
+ b.HasOne("Timeline.Entities.TimelineEntity", "Timeline")
+ .WithMany("Posts")
+ .HasForeignKey("TimelineId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Timeline.Entities.UserAvatarEntity", b =>
+ {
+ b.HasOne("Timeline.Entities.UserEntity", "User")
+ .WithOne("Avatar")
+ .HasForeignKey("Timeline.Entities.UserAvatarEntity", "UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/Timeline/Migrations/20200826164553_TimelineAddTitle.cs b/Timeline/Migrations/20200826164553_TimelineAddTitle.cs
new file mode 100644
index 00000000..7e8c498b
--- /dev/null
+++ b/Timeline/Migrations/20200826164553_TimelineAddTitle.cs
@@ -0,0 +1,22 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace Timeline.Migrations
+{
+ public partial class TimelineAddTitle : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn<string>(
+ name: "title",
+ table: "timelines",
+ nullable: true);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "title",
+ table: "timelines");
+ }
+ }
+}
diff --git a/Timeline/Migrations/DatabaseContextModelSnapshot.cs b/Timeline/Migrations/DatabaseContextModelSnapshot.cs
index 3066dcc0..65ae6c9a 100644
--- a/Timeline/Migrations/DatabaseContextModelSnapshot.cs
+++ b/Timeline/Migrations/DatabaseContextModelSnapshot.cs
@@ -14,7 +14,7 @@ namespace Timeline.Migrations
{
#pragma warning disable 612, 618
modelBuilder
- .HasAnnotation("ProductVersion", "3.1.5");
+ .HasAnnotation("ProductVersion", "3.1.7");
modelBuilder.Entity("Timeline.Entities.DataEntity", b =>
{
@@ -97,6 +97,10 @@ namespace Timeline.Migrations
.HasColumnName("owner")
.HasColumnType("INTEGER");
+ b.Property<string>("Title")
+ .HasColumnName("title")
+ .HasColumnType("TEXT");
+
b.Property<string>("UniqueId")
.IsRequired()
.ValueGeneratedOnAdd()
diff --git a/Timeline/Models/Http/Timeline.cs b/Timeline/Models/Http/Timeline.cs
index 6498fa74..a81b33f5 100644
--- a/Timeline/Models/Http/Timeline.cs
+++ b/Timeline/Models/Http/Timeline.cs
@@ -25,6 +25,10 @@ namespace Timeline.Models.Http
/// If post is of image type. This is the image url.
/// </summary>
public string? Url { get; set; }
+ /// <summary>
+ /// If post has data (currently it means it's a image post), this is the data etag.
+ /// </summary>
+ public string? ETag { get; set; }
}
/// <summary>
@@ -68,6 +72,10 @@ namespace Timeline.Models.Http
/// </summary>
public string UniqueId { get; set; } = default!;
/// <summary>
+ /// Title.
+ /// </summary>
+ public string Title { get; set; } = default!;
+ /// <summary>
/// Name of timeline.
/// </summary>
public string Name { get; set; } = default!;
@@ -188,7 +196,8 @@ namespace Timeline.Models.Http
Url = urlHelper.ActionLink(
action: nameof(TimelineController.PostDataGet),
controller: nameof(TimelineController)[0..^nameof(Controller).Length],
- values: new { Name = source.TimelineName, Id = source.Id })
+ values: new { Name = source.TimelineName, Id = source.Id }),
+ ETag = $"\"{imageContent.DataTag}\""
};
}
else
diff --git a/Timeline/Models/Http/TimelineController.cs b/Timeline/Models/Http/TimelineController.cs
index aad361ee..7bd141ed 100644
--- a/Timeline/Models/Http/TimelineController.cs
+++ b/Timeline/Models/Http/TimelineController.cs
@@ -57,6 +57,11 @@ namespace Timeline.Models.Http
public class TimelinePatchRequest
{
/// <summary>
+ /// New title. Null for not change.
+ /// </summary>
+ public string? Title { get; set; }
+
+ /// <summary>
/// New description. Null for not change.
/// </summary>
public string? Description { get; set; }
@@ -66,4 +71,23 @@ namespace Timeline.Models.Http
/// </summary>
public TimelineVisibility? Visibility { get; set; }
}
+
+ /// <summary>
+ /// Change timeline name request model.
+ /// </summary>
+ public class TimelineChangeNameRequest
+ {
+ /// <summary>
+ /// Old name of timeline.
+ /// </summary>
+ [Required]
+ [TimelineName]
+ public string OldName { get; set; } = default!;
+ /// <summary>
+ /// New name of timeline.
+ /// </summary>
+ [Required]
+ [TimelineName]
+ public string NewName { get; set; } = default!;
+ }
}
diff --git a/Timeline/Models/Timeline.cs b/Timeline/Models/Timeline.cs
index 34c253a0..a5987577 100644
--- a/Timeline/Models/Timeline.cs
+++ b/Timeline/Models/Timeline.cs
@@ -43,6 +43,10 @@ namespace Timeline.Models
public ImageTimelinePostContent(string dataTag) { DataTag = dataTag; }
public string Type { get; } = TimelinePostContentTypes.Image;
+
+ /// <summary>
+ /// The tag of the data. The tag of the entry in DataManager. Also the etag (not quoted).
+ /// </summary>
public string DataTag { get; set; }
}
@@ -74,6 +78,7 @@ namespace Timeline.Models
public string UniqueID { get; set; } = default!;
public string Name { get; set; } = default!;
public DateTime NameLastModified { get; set; } = default!;
+ public string Title { get; set; } = default!;
public string Description { get; set; } = default!;
public User Owner { get; set; } = default!;
public TimelineVisibility Visibility { get; set; }
@@ -86,6 +91,7 @@ namespace Timeline.Models
public class TimelineChangePropertyRequest
{
+ public string? Title { get; set; }
public string? Description { get; set; }
public TimelineVisibility? Visibility { get; set; }
}
diff --git a/Timeline/Services/TimelineService.cs b/Timeline/Services/TimelineService.cs
index 01f7f5fd..4bcae596 100644
--- a/Timeline/Services/TimelineService.cs
+++ b/Timeline/Services/TimelineService.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using Timeline.Entities;
using Timeline.Helpers;
@@ -357,6 +358,21 @@ namespace Timeline.Services
/// <exception cref="ArgumentException">Thrown when timeline name is invalid.</exception>
/// <exception cref="TimelineNotExistException">Thrown when the timeline does not exist.</exception>
Task DeleteTimeline(string timelineName);
+
+ /// <summary>
+ /// Change name of a timeline.
+ /// </summary>
+ /// <param name="oldTimelineName">The old timeline name.</param>
+ /// <param name="newTimelineName">The new timeline name.</param>
+ /// <returns>The new timeline info.</returns>
+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="oldTimelineName"/> or <paramref name="newTimelineName"/> is null.</exception>
+ /// <exception cref="ArgumentException">Thrown when <paramref name="oldTimelineName"/> or <paramref name="newTimelineName"/> is of invalid format.</exception>
+ /// <exception cref="TimelineNotExistException">Thrown when timeline does not exist.</exception>
+ /// <exception cref="EntityAlreadyExistException">Thrown when a timeline with new name already exists.</exception>
+ /// <remarks>
+ /// You can only change name of general timeline.
+ /// </remarks>
+ Task<Models.Timeline> ChangeTimelineName(string oldTimelineName, string newTimelineName);
}
public class TimelineService : ITimelineService
@@ -395,6 +411,7 @@ namespace Timeline.Services
}
}
+ /// Remember to include Members when query.
private async Task<Models.Timeline> MapTimelineFromEntity(TimelineEntity entity)
{
var owner = await _userService.GetUserById(entity.OwnerId);
@@ -405,11 +422,14 @@ namespace Timeline.Services
members.Add(await _userService.GetUserById(memberEntity.UserId));
}
+ var name = entity.Name ?? ("@" + owner.Username);
+
return new Models.Timeline
{
UniqueID = entity.UniqueId,
- Name = entity.Name ?? ("@" + owner.Username),
+ Name = name,
NameLastModified = entity.NameLastModified,
+ Title = string.IsNullOrEmpty(entity.Title) ? name : entity.Title,
Description = entity.Description ?? "",
Owner = owner,
Visibility = entity.Visibility,
@@ -834,6 +854,12 @@ namespace Timeline.Services
var changed = false;
+ if (newProperties.Title != null)
+ {
+ changed = true;
+ timelineEntity.Title = newProperties.Title;
+ }
+
if (newProperties.Description != null)
{
changed = true;
@@ -1102,5 +1128,39 @@ namespace Timeline.Services
_database.Timelines.Remove(entity);
await _database.SaveChangesAsync();
}
+
+ public async Task<Models.Timeline> ChangeTimelineName(string oldTimelineName, string newTimelineName)
+ {
+ if (oldTimelineName == null)
+ throw new ArgumentNullException(nameof(oldTimelineName));
+ if (newTimelineName == null)
+ throw new ArgumentNullException(nameof(newTimelineName));
+
+ ValidateTimelineName(oldTimelineName, nameof(oldTimelineName));
+ ValidateTimelineName(newTimelineName, nameof(newTimelineName));
+
+ var entity = await _database.Timelines.Include(t => t.Members).Where(t => t.Name == oldTimelineName).SingleOrDefaultAsync();
+
+ if (entity == null)
+ throw new TimelineNotExistException(oldTimelineName);
+
+ if (oldTimelineName == newTimelineName)
+ return await MapTimelineFromEntity(entity);
+
+ var conflict = await _database.Timelines.AnyAsync(t => t.Name == newTimelineName);
+
+ if (conflict)
+ throw new EntityAlreadyExistException(EntityNames.Timeline, null, ExceptionTimelineNameConflict);
+
+ var now = _clock.GetCurrentTime();
+
+ entity.Name = newTimelineName;
+ entity.NameLastModified = now;
+ entity.LastModified = now;
+
+ await _database.SaveChangesAsync();
+
+ return await MapTimelineFromEntity(entity);
+ }
}
}
diff --git a/Timeline/Services/UserAvatarService.cs b/Timeline/Services/UserAvatarService.cs
index 2bf8bddc..b41c45fd 100644
--- a/Timeline/Services/UserAvatarService.cs
+++ b/Timeline/Services/UserAvatarService.cs
@@ -71,9 +71,10 @@ namespace Timeline.Services
/// </summary>
/// <param name="id">The id of the user to set avatar for.</param>
/// <param name="avatar">The avatar. Can be null to delete the saved avatar.</param>
+ /// <returns>The etag of the avatar.</returns>
/// <exception cref="ArgumentException">Thrown if any field in <paramref name="avatar"/> is null when <paramref name="avatar"/> is not null.</exception>
/// <exception cref="ImageException">Thrown if avatar is of bad format.</exception>
- Task SetAvatar(long id, Avatar? avatar);
+ Task<string> SetAvatar(long id, Avatar? avatar);
}
// TODO! : Make this configurable.
@@ -199,7 +200,7 @@ namespace Timeline.Services
return defaultAvatar;
}
- public async Task SetAvatar(long id, Avatar? avatar)
+ public async Task<string> SetAvatar(long id, Avatar? avatar)
{
if (avatar != null)
{
@@ -213,11 +214,7 @@ namespace Timeline.Services
if (avatar == null)
{
- if (avatarEntity == null || avatarEntity.DataTag == null)
- {
- return;
- }
- else
+ if (avatarEntity != null && avatarEntity.DataTag != null)
{
await _dataManager.FreeEntry(avatarEntity.DataTag);
avatarEntity.DataTag = null;
@@ -226,6 +223,7 @@ namespace Timeline.Services
await _database.SaveChangesAsync();
_logger.LogInformation(Resources.Services.UserAvatarService.LogUpdateEntity);
}
+ return await _defaultUserAvatarProvider.GetDefaultAvatarETag();
}
else
{
@@ -250,6 +248,8 @@ namespace Timeline.Services
{
await _dataManager.FreeEntry(oldTag);
}
+
+ return avatarEntity.DataTag;
}
}
}