diff --git a/.github/chore/content.sql b/.github/chore/content.sql index 7ed0322d..ecf0f084 100644 --- a/.github/chore/content.sql +++ b/.github/chore/content.sql @@ -11,12 +11,28 @@ * limitations under the License. */ + +CREATE TABLE `notifications` +( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `sender_id` BIGINT NOT NULL, + `receiver_id` BIGINT NOT NULL, + `content` varchar(255) NOT NULL, + `type` TINYINT unsigned NOT NULL DEFAULT 0, + `status` TINYINT unsigned NOT NULL DEFAULT 0, + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `query_by_receiver_id` (`status`,`receiver_id`,`id` ASC) +) ENGINE=InnoDB + DEFAULT CHARSET=utf8mb4 + COLLATE=utf8mb4_0900_ai_ci; + CREATE TABLE videos ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL, title VARCHAR(255) NOT NULL, - des TEXT NOT NULL, + des VARCHAR(255) NOT NULL, cover_url VARCHAR(255) NOT NULL default '', video_url VARCHAR(255) NOT NULL default '', duration INT UNSIGNED, @@ -26,15 +42,15 @@ CREATE TABLE videos updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, processed TINYINT NOT NULL DEFAULT 0, PRIMARY KEY (id), - KEY `user_created` (`processed`, `user_id`, `id` DESC), - KEY `processed` (`processed`, `id` DESC) + KEY `user_created` (`processed`, `user_id`, `id` DESC), + KEY `processed` (`processed`, `id` DESC) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci; CREATE TABLE counter ( - id BIGINT NOT NULL, + id BIGINT NOT NULL, counter INT UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (id) ) ENGINE = InnoDB diff --git a/sharp/content.Tests/repository/NotificationRepositoryTest.cs b/sharp/content.Tests/repository/NotificationRepositoryTest.cs new file mode 100644 index 00000000..0b5fe206 --- /dev/null +++ b/sharp/content.Tests/repository/NotificationRepositoryTest.cs @@ -0,0 +1,85 @@ +namespace content.Tests.repository; + +using MySqlConnector; +using Xunit; +using System.Collections.Generic; +using System.Threading.Tasks; +using content.repository; + +public class NotificationRepositoryTests +{ + private readonly NotificationRepository _repository; + + public NotificationRepositoryTests() + { + var dataSource = new MySqlDataSource(Environment.GetEnvironmentVariable("CONNECTION_STRING") !); + _repository = new NotificationRepository(dataSource); + } + + [Fact] + public async Task FindByReceiverId_ReturnsNotifications_WhenUnreadOnlyIsFalse() + { + // Arrange + const long receiverId = 1L; + const long page = 0L; + const int size = 10; + + // Act + var result = await _repository.FindByReceiverId(receiverId, page, size); + + // Assert + Assert.NotNull(result); + Assert.IsType>(result); + } + + [Fact] + public async Task FindByReceiverId_ReturnsUnreadNotifications_WhenUnreadOnlyIsTrue() + { + // Arrange + const long receiverId = 1L; + const long page = 0L; + const int size = 10; + const bool unreadOnly = true; + + // Act + var result = await _repository.FindByReceiverId(receiverId, page, size, unreadOnly); + + // Assert + Assert.NotNull(result); + Assert.IsType>(result); + } + + [Fact] + public async Task Save_ReturnsNotification_WhenInsertIsSuccessful() + { + // Arrange + var notification = new Message + { + SenderId = 1L, + ReceiverId = 2L, + Content = "Test content", + Type = 1, + }; + + // Act + var result = await _repository.Save(notification); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + } + + [Fact] + public async Task UpdateStatus_UpdatesNotificationStatus() + { + // Arrange + const long id = 1L; + const int status = 1; + + // Act + await _repository.UpdateStatus(id, status); + + // Assert + // No exception means the test passed + } +} \ No newline at end of file diff --git a/sharp/content/repository/Notification.cs b/sharp/content/repository/Notification.cs new file mode 100644 index 00000000..807eba9f --- /dev/null +++ b/sharp/content/repository/Notification.cs @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2023-2024 sixwaaaay. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +using Dapper; +using MySqlConnector; + +namespace content.repository; + +public record Message +{ + public long Id { get; set; } + public long SenderId { get; init; } + public long ReceiverId { get; init; } + public string Content { get; init; } = string.Empty; + public short Type { get; init; } + public short Status { get; init; } + public DateTime CreatedAt { get; init; } = DateTime.Now; +} + +public interface INotificationRepository +{ + ValueTask> FindByReceiverId(long receiverId, long page, int size, bool unreadOnly); + + ValueTask Save(Message notification); + ValueTask UpdateStatus(long id, short status); +} + +public class NotificationRepository(MySqlDataSource dataSource) : INotificationRepository +{ + public async ValueTask> FindByReceiverId(long receiverId, long page, int size, + bool unreadOnly = false) + { + await using var connection = await dataSource.OpenConnectionAsync(); + if (unreadOnly) + { + var messageNotifications = await connection.QueryAsync( + "SELECT id, sender_id, receiver_id, content, type, status, created_at " + + "FROM notifications WHERE receiver_id = @receiverId AND status = 0 " + + "AND id > @page limit @size", new { receiverId, page, size }); + return messageNotifications.ToList(); + } + + var notifications = await connection.QueryAsync( + "SELECT id, sender_id, receiver_id, content, type, status, created_at " + + "FROM notifications WHERE receiver_id = @receiverId " + + "AND id > @page limit @size", + new { receiverId, page, size }); + return notifications.ToList(); + } + + public async ValueTask Save(Message notification) + { + await using var connection = await dataSource.OpenConnectionAsync(); + var result = await connection.ExecuteAsync( + "INSERT INTO notifications (sender_id, receiver_id, content, type, status, created_at) " + + "VALUES (@SenderId, @ReceiverId, @Content, @Type, @Status, @CreatedAt)", notification); + if (result == 0) + { + throw new Exception("Insert notification failed"); + } + + notification.Id = await connection.QuerySingleAsync("SELECT LAST_INSERT_ID()"); + return notification; + } + + public async ValueTask UpdateStatus(long id, short status) + { + await using var connection = await dataSource.OpenConnectionAsync(); + await connection.ExecuteAsync( + "UPDATE notifications SET status = @status WHERE id = @id", new { id, status }); + } +} + +public static class NotificationRepositoryExtension +{ + public static IServiceCollection AddNotificationRepository(this IServiceCollection services) => + services.AddSingleton(); +} \ No newline at end of file