public async Task CreateOrUpdateUserAsync(User user)
        {
            if (user == null)
            {
                throw new InvalidOperationException("User is null");
            }

            if (string.IsNullOrEmpty(user.PartitionKey))
            {
                user.PartitionKey = GetUserPartitionKeyFromId(user.Id);
            }

            if (!user.IsValid())
            {
                throw new InvalidOperationException("User is not valid. Check that all required properties are set");
            }

            var container    = Server.Instance.Database.GetContainer(Constants.UsersContainerName);
            var partitionKey = new PartitionKey(user.PartitionKey);
            var response     = await container.UpsertItemAsync(user, partitionKey);

            var createdItem = response.StatusCode == HttpStatusCode.Created;

            Log.Debug("UserServer.CreateOrUpdateUserAsync: Created user? " + createdItem);
            Log.Debug($"UserServer.CreateOrUpdateUserAsync: Request charge: {response.RequestCharge}. Elapsed time: {response.Diagnostics.GetClientElapsedTime()} ms");
        }
        public async Task <int> GetUserGalleryCountAsync(User user)
        {
            if (user == null)
            {
                throw new InvalidOperationException("User is null");
            }

            var query = new QueryDefinition("SELECT VALUE COUNT(1) FROM c WHERE c.CreatedByUserId = @userId").WithParameter("@userId", user.Id);

            return(await Server.Instance.Galleries.GetGalleriesScalarByQueryAsync(query));
        }
        /// <summary>
        /// Deletes a user from the database and any references to the user, which anonymises any content they've created.
        /// This balances the need for a user's right for their personal data to be deleted (GDPR compliance, etc.) with ensuring the integrity of content.
        /// </summary>
        public async Task DeleteUserAsync(User user)
        {
            if (user == null)
            {
                throw new ArgumentNullException(nameof(user));
            }

            // get galleries where the user has been involved in some way (author/comments)
            var query           = "SELECT * FROM c WHERE c.CreatedByUserId = @userId OR c.Comments.CreatedByUserId = @userId";
            var queryDefinition = new QueryDefinition(query).WithParameter("@userId", user.Id);
            var galleries       = await Server.Instance.Galleries.GetGalleriesByQueryAsync(queryDefinition);

            // go through the galleries and look for user references and remove them then update the gallery
            foreach (var gallery in galleries)
            {
                // remove any references to gallery creations
                if (gallery.CreatedByUserId == user.Id)
                {
                    gallery.CreatedByUserId = Constants.AnonUserId;
                }

                // remove any references to gallery comments
                foreach (var galleryComment in gallery.Comments.Where(q => q.CreatedByUserId == user.Id))
                {
                    galleryComment.CreatedByUserId = Constants.AnonUserId;
                }

                await Server.Instance.Galleries.UpdateGalleryAsync(gallery);
            }

            // go through any comments the user has left against images and anonymise those
            var images = await Server.Instance.Images.GetImagesUserHasCommentedOnAsync(user.Id);

            foreach (var image in images)
            {
                foreach (var comment in image.Comments.Where(c => c.CreatedByUserId == user.Id))
                {
                    comment.CreatedByUserId = Constants.AnonUserId;
                }

                await Server.Instance.Images.UpdateImageAsync(image);
            }

            // delete the user
            var container = Server.Instance.Database.GetContainer(Constants.UsersContainerName);
            var result    = await container.DeleteItemAsync <User>(user.Id, new PartitionKey(user.PartitionKey));

            Log.Debug("UserServer:DeleteUserAsync: Status code: " + result.StatusCode);
            Log.Debug("UserServer:DeleteUserAsync: Request charge: " + result.RequestCharge);
        }
        /// <summary>
        /// If the user's profile picture is not being internally hosted then this method will download it and store it internally
        /// </summary>
        /// <param name="user">The user who to download the picture for.</param>
        /// <param name="originalPictureUrl">The URL for the original version of the picture.</param>
        public async Task DownloadAndStoreUserPictureAsync(User user, string originalPictureUrl)
        {
            if (user.Picture.HasValue() && user.Picture.Equals(originalPictureUrl, StringComparison.CurrentCultureIgnoreCase))
            {
                return;
            }

            // picture URL is different, download and store it
            var containerClient = Server.Instance.BlobServiceClient.GetBlobContainerClient(Constants.StorageUserPicturesContainerName);

            // delete any old version we have currently
            string fileId;

            if (user.PictureHostedUrl.HasValue())
            {
                fileId = user.PictureHostedUrl.Substring(user.PictureHostedUrl.LastIndexOf('/') + 1);
                await containerClient.DeleteBlobIfExistsAsync(fileId);
            }

            // download it and store the original picture
            fileId           = $"{user.Id}-{DateTime.Now.Ticks}.jpg";
            using var client = new HttpClient();

            try
            {
                await using var imageStream = await client.GetStreamAsync(originalPictureUrl);

                await containerClient.UploadBlobAsync(fileId, imageStream);

                user.PictureHostedUrl = containerClient.Uri + "/" + fileId;
                user.Picture          = originalPictureUrl;
            }
            catch (Exception e)
            {
                Log.Debug($"UserServer.DownloadAndStoreUserPictureAsync() - Exception downloading/uploading: {e.Message}");
            }
        }