diff --git a/changelog.md b/changelog.md
index ffcbfce6..25cd5fec 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,9 @@
# Changelog for Weavy
+## 8.7.0 (2021-11-02)
+
+* Added Conversations API with functionality for getting, creating and updating Weavy conversations. For more information, check out the api documentation at https://[weavy_url]/api.
+
## 8.6.8 (2021-09-21)
* Fix for server error when fetching spaces.
diff --git a/lib/Weavy.Bundler.dll b/lib/Weavy.Bundler.dll
index c65b3e0e..880c5610 100644
Binary files a/lib/Weavy.Bundler.dll and b/lib/Weavy.Bundler.dll differ
diff --git a/lib/Weavy.Bundler.pdb b/lib/Weavy.Bundler.pdb
index e16b5183..05ce018a 100644
Binary files a/lib/Weavy.Bundler.pdb and b/lib/Weavy.Bundler.pdb differ
diff --git a/lib/Weavy.Core.dll b/lib/Weavy.Core.dll
index 77c1c1f6..b95aa1b2 100644
Binary files a/lib/Weavy.Core.dll and b/lib/Weavy.Core.dll differ
diff --git a/lib/Weavy.Core.pdb b/lib/Weavy.Core.pdb
index ecb596f3..4d564efe 100644
Binary files a/lib/Weavy.Core.pdb and b/lib/Weavy.Core.pdb differ
diff --git a/lib/Weavy.Web.dll b/lib/Weavy.Web.dll
index 13e19ced..2b53ee7e 100644
Binary files a/lib/Weavy.Web.dll and b/lib/Weavy.Web.dll differ
diff --git a/lib/Weavy.Web.pdb b/lib/Weavy.Web.pdb
index c00ca5a8..22dc2dc9 100644
Binary files a/lib/Weavy.Web.pdb and b/lib/Weavy.Web.pdb differ
diff --git a/lib/Weavy.Web.xml b/lib/Weavy.Web.xml
index 52d88a49..9e2235bf 100644
--- a/lib/Weavy.Web.xml
+++ b/lib/Weavy.Web.xml
@@ -6040,6 +6040,79 @@
Performs custom validation.
+
+
+ Model for creating a conversation.
+
+
+
+
+ The members of the conversation.
+
+
+
+
+ View model for a conversation.
+
+
+
+
+ Gets the id of the conversation.
+
+
+
+
+ Gets or sets the title of the conversation.
+
+
+
+
+ Gets the avatar url.
+
+
+
+
+ Gets a value indicating the presence status of the other user in a one-to-one conversation.
+
+
+
+
+ Gets a value indicating whether this is a chat room or a conversation between just 2 people.
+
+
+
+
+ Gets a value indicating if the current user has read all messages in the conversation.
+
+
+
+
+ Gets a value indicating if the current user has pinned the conversation.
+
+
+
+
+ Gets a value indicating if the current user has starred the conversation.
+
+
+
+ Gets a snippet of the most recent message in the conversation.
+
+
+
+ Gets the date and time when the last message in the convesation was recieved or null
if no messages exists in the conversation.
+
+
+
+
+ Gets a string representation of when the created date occured, e.g. "4:38 PM", "Yesterday", "Wednesday", "June 16", "10/06/16" etc
+
+
+
+
+ Gets the list of users that are members of the conversation.
+
+
Model for creating new meetings
@@ -6105,6 +6178,101 @@
+
+
+ Model for inserting a message into a conversation.
+
+
+
+
+ The message text.
+
+
+
+
+ View model for a message.
+
+
+
+
+ Gets the id of the message.
+
+
+
+
+ Gets the date when the message was created
+
+
+
+
+ Gets the user's id that created the message
+
+
+
+
+ Gets the user's name that created the message
+
+
+
+
+ Gets the user's thumb that created the message
+
+
+
+
+ Gets the attachments for the message
+
+
+
+
+ Gets the html to display
+
+
+
+
+ A list om conversation members that have seen the message
+
+
+
+
+ Model for updating the name of a room.
+
+
+
+
+ The new room name.
+
+
+
+
+ Model for settings.
+
+
+
+
+ Gets or sets the user time zone.
+
+
+
+
+ Gets or sets a value indicating whether Enter should send a message or insert a new line.
+
+
+
+
+ The blob id of the avatar to use or null if no avatar is set.
+
+
+
+
+ The avatar thumb.
+
+
+
+
+ A list with all available time zones.
+
+
A model representing a Zoom webhook event
diff --git a/lib/wvy.exe b/lib/wvy.exe
index a26c2fca..35a66d38 100644
Binary files a/lib/wvy.exe and b/lib/wvy.exe differ
diff --git a/lib/wvy.pdb b/lib/wvy.pdb
index 94d93fbc..4606b7e5 100644
Binary files a/lib/wvy.pdb and b/lib/wvy.pdb differ
diff --git a/src/Areas/Api/Controllers/AppsController.cs b/src/Areas/Api/Controllers/AppsController.cs
index f869f761..91cfe2fd 100644
--- a/src/Areas/Api/Controllers/AppsController.cs
+++ b/src/Areas/Api/Controllers/AppsController.cs
@@ -40,7 +40,6 @@ public class AppsController : WeavyApiController {
/// The to insert.
///
/// POST /api/spaces/1/apps
- ///
/// {
/// "name": "Files",
/// "guid": "523edd88-4bbf-4547-b60f-2859a6d2ddc1"
@@ -68,7 +67,6 @@ public class AppsController : WeavyApiController {
/// Contains the new properties for the app.
///
/// PATCH /api/spaces/527/apps
- ///
/// {
/// "name": "Files"
/// }
diff --git a/src/Areas/Api/Controllers/BlobsController.cs b/src/Areas/Api/Controllers/BlobsController.cs
new file mode 100644
index 00000000..ce17afb6
--- /dev/null
+++ b/src/Areas/Api/Controllers/BlobsController.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using System.Web.Http;
+using System.Web.Http.Description;
+using Weavy.Core;
+using Weavy.Core.Models;
+using Weavy.Core.Services;
+using Weavy.Web.Api.Controllers;
+using Weavy.Web.Api.Models;
+using Weavy.Web.Api.Streamers;
+
+namespace Weavy.Areas.Api.Controllers {
+
+ ///
+ /// Api controller for manipulating Blobs.
+ ///
+ [RoutePrefix("api")]
+ public class BlobsController : WeavyApiController {
+
+ ///
+ /// Uploads new blob(s). Use multipart/form-data for the request format.
+ /// After upload the blobs can be used for setting avatars or as references when creating attachments and/or files.
+ ///
+ /// The uploaded blobs(s).
+ [HttpPost]
+ [Route("blobs")]
+ public async Task> Upload() {
+ // check if the request contains multipart/form-data.
+ if (!Request.Content.IsMimeMultipartContent()) {
+ throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
+ }
+
+ // write uploaded files to local disk cache
+ var provider = await Request.Content.ReadAsMultipartAsync(new BlobMultipartFormDataRemoteStreamProvider());
+
+ // iterate over the uploaded files and store them as blobs in the database
+ List blobs = new List();
+ foreach (var data in provider.FileData) {
+ var blob = provider.GetBlob(data);
+ if (blob != null) {
+ try {
+ blob = BlobService.Insert(blob, System.IO.File.OpenRead(data.Location));
+ blobs.Add(blob);
+ } catch {
+ ThrowResponseException(HttpStatusCode.InternalServerError, "Failed to upload blobs");
+ }
+ }
+ }
+ return new ScrollableList(blobs, null, null, blobs.Count(), Request.RequestUri);
+ }
+ }
+}
diff --git a/src/Areas/Api/Controllers/ConversationsController.cs b/src/Areas/Api/Controllers/ConversationsController.cs
new file mode 100644
index 00000000..0f8cebbd
--- /dev/null
+++ b/src/Areas/Api/Controllers/ConversationsController.cs
@@ -0,0 +1,426 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Web.Http;
+using System.Web.Http.Description;
+using Weavy.Core;
+using Weavy.Core.Localization;
+using Weavy.Core.Models;
+using Weavy.Core.Services;
+using Weavy.Core.Utils;
+using Weavy.Web.Api.Controllers;
+using Weavy.Web.Api.Models;
+using Weavy.Web.Models;
+
+namespace Weavy.Areas.Api.Controllers {
+
+ ///
+ /// Api controller for manipulating Conversations.
+ ///
+ [RoutePrefix("api")]
+ public class ConversationsController : WeavyApiController {
+
+ private static readonly StringLocalizer T = StringLocalizer.CreateInstance();
+
+ ///
+ /// Get the conversation with the specified id.
+ ///
+ /// The conversation id.
+ /// GET /api/conversations/527
+ /// The conversation.
+ [HttpGet]
+ [ResponseType(typeof(ConversationOutModel))]
+ [Route("conversations/{id:int}")]
+ public IHttpActionResult Get(int id) {
+
+ // read conversation
+ ConversationService.SetRead(id, DateTime.UtcNow);
+ var c = GetConversation(id);
+ var conversationOut = GetConversationOut(c);
+
+ // copy members property
+ conversationOut.Members = c.Members;
+
+ return Ok(conversationOut);
+ }
+
+ ///
+ /// Get all conversations for the current user.
+ ///
+ /// GET /api/conversations
+ /// The users conversations.
+ [HttpGet]
+ [ResponseType(typeof(List))]
+ [Route("conversations")]
+ public IHttpActionResult List(Query query) {
+ var result = ConversationService.Search(new ConversationQuery(query) { OrderBy = "PinnedAt DESC, LastMessageAt DESC" });
+ var conversations = new List();
+
+ foreach (var c in result) {
+ var conversationOut = GetConversationOut(c);
+ conversations.Add(conversationOut);
+ }
+ return Ok(conversations);
+ }
+
+ ///
+ /// Create a new or get the existing conversation between the current and specified user.
+ ///
+ /// The to insert.
+ /// The conversation.
+ [HttpPost]
+ [ResponseType(typeof(Conversation))]
+ [Route("conversations")]
+ public IHttpActionResult Create(ConversationInModel model) {
+ string name = null;
+ if (model.Members.Count() > 1) {
+ name = string.Join(", ", model.Members.Select(u => UserService.Get(u).GetTitle()));
+ }
+
+ // create new room or one-on-one conversation or get the existing one
+ return Ok(ConversationService.Insert(new Conversation() { Name = name }, model.Members));
+ }
+
+ ///
+ /// Get the messages in the specified conversation.
+ ///
+ /// The conversation id.
+ /// Query options for paging, sorting etc.
+ /// Returns a potentially paged list of the messages in the conversation.
+ [HttpGet]
+ [ResponseType(typeof(ScrollableList))]
+ [Route("conversations/{id:int}/messages")]
+ public IHttpActionResult GetMessages(int id, QueryOptions opts) {
+ var messagesOut = new List();
+ var conversation = GetConversation(id);
+ var messages = ConversationService.GetMessages(id, opts);
+ foreach (var m in messages) {
+ var seenBy = GetSeenBy(m, messages, conversation.Members);
+ var messageOut = GetMessageOut(m, seenBy);
+ messagesOut.Add(messageOut);
+ }
+ messagesOut.Reverse();
+
+ return Ok(new ScrollableList(messagesOut, null, null, null, Request.RequestUri));
+ }
+
+ ///
+ /// Creates a new message in the specified conversation.
+ ///
+ ///
+ ///
+ ///
+ [HttpPost]
+ [ResponseType(typeof(Message))]
+ [Route("conversations/{id:int}/messages")]
+ public IHttpActionResult InsertMessage(int id, MessageInModel model) {
+ var conversation = GetConversation(id);
+ return Ok(MessageService.Insert(new Message { Text = model.Text, }, conversation));
+ }
+
+ ///
+ /// Called by current user to indicate that they are typing in a conversation.
+ ///
+ /// Id of conversation.
+ ///
+ [HttpPost]
+ [ResponseType(typeof(Conversation))]
+ [Route("conversations/{id:int}/typing")]
+ public IHttpActionResult StartTyping(int id) {
+ var conversation = GetConversation(id);
+ // push typing event to other conversation members
+ PushService.PushToUsers(PushService.EVENT_TYPING, new { Conversation = id, User = WeavyContext.Current.User, Name = WeavyContext.Current.User.Profile.Name ?? WeavyContext.Current.User.Username }, conversation.MemberIds.Where(x => x != WeavyContext.Current.User.Id));
+ return Ok(conversation);
+ }
+
+ ///
+ /// Add members to the conversation.
+ ///
+ /// Id of conversation.
+ /// Ids of users to add.
+ /// 200 OK
+ [HttpPost]
+ [ResponseType(typeof(Conversation))]
+ [Route("conversations/{id:int}/members")]
+ public IHttpActionResult AddMembers(int id, [FromBody]int[] users) {
+ foreach (var userId in users) {
+ ConversationService.AddMember(id, userId);
+ }
+ return Ok(GetConversation(id));
+ }
+
+ ///
+ /// Remove members from the conversation.
+ ///
+ /// Id of conversation.
+ /// Id of member to remove from conversation.
+ /// 200 OK
+ [HttpDelete]
+ [ResponseType(typeof(Conversation))]
+ [Route("conversations/{id:int}/members/{user:int}")]
+ public IHttpActionResult RemoveMember(int id, int user) {
+ ConversationService.RemoveMember(id, user);
+ return Ok(GetConversation(id));
+ }
+
+ ///
+ /// Set the room name.
+ ///
+ /// Id of conversation.
+ /// Room name, or null to remove existing name.
+ /// The updated conversation.
+ [HttpPut]
+ [ResponseType(typeof(Conversation))]
+ [Route("conversations/{id:int}/name")]
+ public IHttpActionResult SetName(int id, NameInModel model) {
+ var conversation = GetConversation(id);
+ if (conversation == null || !conversation.IsRoom) {
+ ThrowResponseException(HttpStatusCode.NotFound, "Conversation " + id + " not found.");
+ }
+ if (model.Name.IsNullOrWhiteSpace()) {
+ model.Name = null;
+ }
+ conversation.Name = model.Name;
+ conversation = ConversationService.Update(conversation);
+
+ // HACK: set Name to GetTitle() so that returned json has conversation title in the name property
+ conversation.Name = conversation.GetTitle();
+ return Ok(conversation);
+ }
+
+ ///
+ /// Called by current user to indicate that they are no longer typing.
+ ///
+ ///
+ /// The conversation.
+ [HttpDelete]
+ [ResponseType(typeof(Conversation))]
+ [Route("conversations/{id:int}/typing")]
+ public IHttpActionResult StopTyping(int id) {
+ var conversation = GetConversation(id);
+ // push typing event to other conversation members
+ PushService.PushToUsers("typing-stopped.weavy", new { Conversation = id, User = WeavyContext.Current.User, Name = WeavyContext.Current.User.Profile.Name ?? WeavyContext.Current.User.Username }, conversation.MemberIds.Where(x => x != WeavyContext.Current.User.Id));
+ return Ok(conversation);
+ }
+
+ ///
+ /// Marks a conversation as read for the current user.
+ ///
+ /// Id of the conversation to mark as read.
+ /// The read conversation.
+ [HttpPost]
+ [ResponseType(typeof(Conversation))]
+ [Route("conversations/{id:int}/read")]
+ public IHttpActionResult Read(int id) {
+ ConversationService.SetRead(id, readAt: DateTime.UtcNow);
+ return Ok(ConversationService.Get(id));
+ }
+
+ ///
+ /// Mark specified conversation as unread.
+ ///
+ /// Conversation id.
+ /// 200 OK
+ [HttpPost]
+ [ResponseType(typeof(Conversation))]
+ [Route("conversations/{id:int}/unread")]
+ public IHttpActionResult SetUnread(int id) {
+ ConversationService.SetRead(id, null);
+ return Ok(ConversationService.Get(id));
+ }
+
+ ///
+ /// Get the number of unread conversations.
+ ///
+ /// The number of unread conversations.
+ [HttpGet]
+ [ResponseType(typeof(int))]
+ [Route("conversations/unread")]
+ public IHttpActionResult GetUnread() {
+ return Ok(ConversationService.GetUnread().Count());
+ }
+
+ ///
+ /// Pins the conversation.
+ ///
+ /// Conversation id.
+ /// 200 OK
+ [HttpPost]
+ [Route("conversations/{id:int}/pin")]
+ public IHttpActionResult Pin(int id) {
+ ConversationService.SetPinned(id, DateTime.UtcNow);
+ return Ok();
+ }
+
+ ///
+ /// Unpins the conversation.
+ ///
+ /// Conversation id.
+ /// 200 OK
+ [HttpPost]
+ [Route("conversations/{id:int}/unpin")]
+ public IHttpActionResult UnPin(int id) {
+ ConversationService.SetPinned(id, null);
+ return Ok();
+ }
+
+ ///
+ /// Stars the conversation.
+ ///
+ /// Conversation id.
+ /// 200 OK
+ [HttpPost]
+ [Route("conversations/{id:int}/star")]
+ public IHttpActionResult Star(int id) {
+ var conversation = GetConversation(id);
+ EntityService.Star(conversation);
+ return Ok();
+ }
+
+ ///
+ /// Unstars the conversation.
+ ///
+ /// Conversation id.
+ /// 200 OK
+ [HttpPost]
+ [Route("conversations/{id:int}/unstar")]
+ public IHttpActionResult UnStar(int id) {
+ var conversation = GetConversation(id);
+ EntityService.Unstar(conversation);
+ return Ok();
+ }
+
+ ///
+ /// Returns the user settings.
+ ///
+ /// The user settings.
+ [HttpGet]
+ [ResponseType(typeof(SettingsModel))]
+ [Route("conversations/settings")]
+ public IHttpActionResult GetSettings() {
+ var settings = new SettingsModel {
+ EnterToSend = User.Profile.Value(UserUtils.EnterToSendKey) ?? false,
+ AvatarId = User.Avatar?.Id,
+ ThumbnailUrl = User.AvatarUrl(size: 128),
+ TimeZones = TimeZoneInfo.GetSystemTimeZones().Select(x => new { Label = x.DisplayName, Value = x.Id })
+ };
+
+ if (User.Profile.Value(UserUtils.TimeZoneKey) != null) {
+ var tz = TimeZoneInfo.GetSystemTimeZones().FirstOrDefault(x => x.Id == User.Profile.Value(UserUtils.TimeZoneKey));
+ if (tz != null) {
+ settings.TimeZone = new { Label = tz.DisplayName, Value = tz.Id };
+ }
+ }
+ return Ok(settings);
+ }
+
+ ///
+ /// Saves the settings.
+ ///
+ /// The updated settings.
+ [HttpPost]
+ [ResponseType(typeof(SettingsModel))]
+ [Route("conversations/settings")]
+ public IHttpActionResult SaveSettings(SettingsModel settings) {
+ User.Profile[UserUtils.TimeZoneKey] = settings.TimeZone;
+ User.Profile[UserUtils.EnterToSendKey] = settings.EnterToSend;
+
+ if (settings.AvatarId == null) {
+ User.Avatar = null;
+ } else if (settings.AvatarId.Value != User.Avatar?.Id) {
+ var blob = BlobService.Get(settings.AvatarId.Value);
+
+ if (blob != null) {
+ User.Avatar = blob;
+ }
+ }
+ UserService.Update(User);
+ return Ok(settings);
+ }
+
+ ///
+ /// Gets a conversation by its id. Throws a 404 http error if the conversation is not found.
+ ///
+ ///
+ private Conversation GetConversation(int id) {
+ var conversation = ConversationService.Get(id);
+ if (conversation == null) {
+ ThrowResponseException(HttpStatusCode.NotFound, "Conversation with id " + id + " not found");
+ }
+ return conversation;
+ }
+
+ ///
+ /// Returns a object from a .
+ ///
+ ///
+ ///
+ private ConversationOutModel GetConversationOut(Conversation conversation) {
+ var conversationOut = new ConversationOutModel() {
+ Id = conversation.Id,
+ AvatarUrl = conversation.AvatarUrl(48),
+ Title = conversation.GetTitle(),
+ IsPinned = conversation.IsPinned,
+ IsStarred = conversation.IsStarred(),
+ IsRead = conversation.IsRead,
+ IsRoom = conversation.IsRoom,
+ Excerpt = (conversation.LastMessage?.CreatedById == User.Id ? T["You:"] + " " : "") + conversation.GetExcerpt(true)
+ };
+
+ if (!conversation.IsRoom) {
+ conversationOut.Presence = conversation.Members.FirstOrDefault(x => x.Id != User.Id)?.Presence;
+ }
+
+ if (conversation.LastMessage != null) {
+ var local = conversation.LastMessage.CreatedAt.ToLocal();
+ string formatted = conversation.LastMessage.CreatedAt.When();
+ conversationOut.LastMessageAt = local;
+ conversationOut.LastMessageAtString = formatted;
+ }
+ return conversationOut;
+ }
+
+ ///
+ /// Returns a object from a .
+ ///
+ ///
+ ///
+ ///
+ private MessageOutModel GetMessageOut(Message message, IEnumerable seenBy) {
+ return new MessageOutModel {
+ Id = message.Id,
+ CreatedAt = message.CreatedAt,
+ CreatedById = message.CreatedBy().Id,
+ CreatedByName = message.CreatedBy().Profile.Name,
+ CreatedByThumb = message.CreatedBy().ThumbPlaceholderUrl(),
+ Attachments = message.Attachments(),
+ Html = message.Html.ToAbsoluteUrls(),
+ SeenBy = seenBy
+ };
+ }
+
+ ///
+ /// Helper for displaying seen by indicator.
+ ///
+ /// The message for which to get seen by indicator.
+ /// The messages in the conversation.
+ /// The members in the conversation.
+ ///
+ private IEnumerable GetSeenBy(Message message, IEnumerable messages, IEnumerable members) {
+
+ // get messages created after timmestamp
+ var after = messages.Where(x => x.CreatedAt > message.CreatedAt);
+
+ // get other members
+ var others = members.Where(x => x.Id != WeavyContext.Current.User.Id);
+
+ // return member if message is read by member and there are no later messages read by or created by member
+ foreach (ConversationMember m in others) {
+ if (m.ReadAt >= message.CreatedAt && !after.Any(x => m.ReadAt >= x.CreatedAt || m.Id == x.CreatedById)) {
+ yield return m;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Areas/Api/Controllers/SearchController.cs b/src/Areas/Api/Controllers/SearchController.cs
index 2bd00dce..2c4fad54 100644
--- a/src/Areas/Api/Controllers/SearchController.cs
+++ b/src/Areas/Api/Controllers/SearchController.cs
@@ -1,7 +1,6 @@
using System.Net;
using System.Web.Http;
using System.Web.Http.Description;
-using Weavy.Areas.Api.Models;
using Weavy.Core.Models;
using Weavy.Core.Services;
using Weavy.Core.Utils;
diff --git a/src/Areas/Api/Controllers/SpacesController.cs b/src/Areas/Api/Controllers/SpacesController.cs
index cf8d7b8f..c2751664 100644
--- a/src/Areas/Api/Controllers/SpacesController.cs
+++ b/src/Areas/Api/Controllers/SpacesController.cs
@@ -2,7 +2,6 @@
using System.Net;
using System.Web.Http;
using System.Web.Http.Description;
-using Weavy.Areas.Api.Models;
using Weavy.Core.Models;
using Weavy.Core.Services;
using Weavy.Web.Api.Controllers;
diff --git a/src/Areas/Api/Controllers/UsersController.cs b/src/Areas/Api/Controllers/UsersController.cs
index fd656013..9f598485 100644
--- a/src/Areas/Api/Controllers/UsersController.cs
+++ b/src/Areas/Api/Controllers/UsersController.cs
@@ -3,7 +3,7 @@
using System.Net;
using System.Web.Http;
using System.Web.Http.Description;
-using Weavy.Areas.Api.Models;
+using Weavy.Core;
using Weavy.Core.Models;
using Weavy.Core.Services;
using Weavy.Core.Utils;
@@ -148,5 +148,15 @@ public class UsersController : WeavyApiController {
var result = UserService.Search(query);
return Ok(new ScrollableList(result, Request.RequestUri));
}
+
+ ///
+ /// Retrieves the current user.
+ ///
+ /// Returns a user.
+ [HttpGet]
+ [Route("users/me")]
+ public User GetMe() {
+ return UserService.Get(WeavyContext.Current.User.Id);
+ }
}
}
diff --git a/src/Areas/Api/Models/SpaceMember.cs b/src/Areas/Api/Models/SpaceMember.cs
deleted file mode 100644
index 32f771b8..00000000
--- a/src/Areas/Api/Models/SpaceMember.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using Weavy.Core.Models;
-
-namespace Weavy.Areas.Api.Models {
-
- ///
- /// Model for a space member.
- ///
- public class SpaceMember {
-
- ///
- /// The id of the user to update.
- ///
- public int UserId { get; set; }
-
- ///
- /// The access level for the user.
- ///
- public Access Access { get; set; }
- }
-}
diff --git a/src/Areas/Api/Models/UserIn.cs b/src/Areas/Api/Models/UserIn.cs
deleted file mode 100644
index 51c1d788..00000000
--- a/src/Areas/Api/Models/UserIn.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-using System.Collections.Generic;
-using Weavy.Core.Models;
-using Weavy.Core.Utils;
-using DA = System.ComponentModel.DataAnnotations;
-
-namespace Weavy.Areas.Api.Models {
-
- ///
- /// An object used to create and update a user.
- ///
- public class UserIn : DA.IValidatableObject {
-
- ///
- /// The blob id of avatar to use for the profile image.
- ///
- public int? AvatarId { get; set; }
-
- ///
- /// The name of the user.
- ///
- [StringLength(256)]
- public string Name { get; set; }
-
- ///
- /// The email address for the user.
- ///
- [EmailAddress]
- [StringLength(256)]
- public string Email { get; set; }
-
- ///
- /// The username.
- ///
- [Required]
- [RegularExpression(HtmlExtensions.UsernamePattern, ErrorMessage = "Invalid username. Valid characters are [a-zA-Z0-9_].")]
- [StringLength(32)]
- public string Username { get; set; }
-
- ///
- /// The password to set for the user.
- ///
- public string Password { get; set; }
-
- ///
- /// A comment about the user.
- ///
- public string Comment { get; set; }
-
- ///
- /// The id of the directory where the user should be added.
- ///
- public int? DirectoryId { get; set; }
-
- ///
- /// A flag indicating if the user should have admin privaligies.
- ///
- public bool IsAdmin { get; set; }
-
- ///
- /// A flag indicating if the user is suspended.
- ///
- public bool IsSuspended { get; set; }
-
- ///
- ///
- ///
- public virtual IEnumerable Validate(DA.ValidationContext validationContext) {
- // validate password complexity
- if (Password != null) {
- foreach (var res in ValidationUtils.ValidatePassword(Password)) {
- yield return new DA.ValidationResult(res.ErrorMessage, new[] { nameof(Password) });
- }
- }
- }
- }
-}
diff --git a/src/Properties/AssemblyInfo.cs b/src/Properties/AssemblyInfo.cs
index 1314d17e..12bf5c68 100644
--- a/src/Properties/AssemblyInfo.cs
+++ b/src/Properties/AssemblyInfo.cs
@@ -17,4 +17,4 @@
[assembly: Guid("70cffd0f-7b12-43ec-9c57-9080937a6b04")]
// Assembly version
-[assembly: AssemblyVersion("8.6.8")]
+[assembly: AssemblyVersion("8.7.0")]
diff --git a/src/Resources/Resources.txt b/src/Resources/Resources.txt
index 09163016..523b7714 100644
--- a/src/Resources/Resources.txt
+++ b/src/Resources/Resources.txt
@@ -826,6 +826,7 @@ You must specify user id or role id=
You need to specify at least one recipient.=
You should specify either user id or role id=
You started editing this {0} {1}=
+You:=
You’ll get a notification when someone updates or comments on this {0}.=
Your comment...=
Your computer=
diff --git a/src/Weavy.csproj b/src/Weavy.csproj
index 1efd8aca..23a51b79 100644
--- a/src/Weavy.csproj
+++ b/src/Weavy.csproj
@@ -1,4 +1,4 @@
-
+
@@ -6,7 +6,8 @@
Debug
AnyCPU
-
+
+
2.0
{70CFFD0F-7B12-43EC-9C57-9080937A6B04}
{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}
@@ -22,7 +23,8 @@
-
+
+
true
@@ -364,17 +366,17 @@
-
+
..\lib\Weavy.Bundler.dll
-
+
..\lib\Weavy.Core.dll
-
+
..\lib\Weavy.Web.dll
-
+
..\lib\wvy.exe
@@ -4309,11 +4311,11 @@
+
+
-
-
@@ -4361,12 +4363,13 @@
True
True
- 0
+ 58586
/
https://localhost:44323/
False
False
-
+
+
False
@@ -4417,7 +4420,7 @@ using System.Reflection%3B
-
+
\ No newline at end of file
diff --git a/tools/Weavy.Build.dll b/tools/Weavy.Build.dll
index 16b2b097..8ed128dc 100644
Binary files a/tools/Weavy.Build.dll and b/tools/Weavy.Build.dll differ