Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix some issues #58

Merged
merged 13 commits into from
Dec 21, 2024
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# TelegramAsDatabase
The official repository for telegram as database
### Now telegram as database (TDB) is availabe on nuget.org: [click here](https://www.nuget.org/packages/TelegramAsDatabase)
# TDB: Telegram as database service
This project is a .NET application that utilizes Telegram's free cloud storage to provide a simple, scalable database solution. It enables data storage and management through a Telegram bot, allowing users to interact with the database directly via Telegram. By leveraging Telegram's cloud infrastructure, the project eliminates the need for traditional database setups, offering a lightweight and efficient alternative for small to medium-scale data storage needs.

### Now telegram as database is availabe on nuget.org: [click here](https://www.nuget.org/packages/TelegramAsDatabase)

# Getting start
1. Create a bot using bot father in telegram (Important: **Dont Change the bot description at all**)
Expand Down
6 changes: 3 additions & 3 deletions TelegramAsDatabase.sln
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{16AB618A-A
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TelegramAsDatabase", "src\TelegramAsDatabase\TelegramAsDatabase.csproj", "{C7B425F1-D3F4-4265-9777-1608053FD442}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Integration", "Integration", "{6C2A317A-12B4-4C62-B516-38368666FDEE}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "integrations", "integrations", "{6C2A317A-12B4-4C62-B516-38368666FDEE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Integration.WebApi", "test\Integration\Test.Integration.WebApi\Test.Integration.WebApi.csproj", "{7F5D2A60-6C08-4364-8BE3-0D1CFF67DA7E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTest", "UnitTest", "{6D3D7A1F-D8DB-41C7-893C-9F5FF930CBAB}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Units", "Units", "{6D3D7A1F-D8DB-41C7-893C-9F5FF930CBAB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.UnitTests", "test\UnitTest\Test.UnitTests\Test.UnitTests.csproj", "{4E947B08-EEB1-4764-9AB0-430CB823B879}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test.Units", "test\UnitTest\Test.UnitTests\Test.Units.csproj", "{4E947B08-EEB1-4764-9AB0-430CB823B879}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TelegramAsDatabase.Extensions", "src\TelegramAsDatabase.Extensions\TelegramAsDatabase.Extensions.csproj", "{3D3A376C-A432-4E74-95AD-83B9C2EB4A0C}"
EndProject
Expand Down
2 changes: 2 additions & 0 deletions TelegramAsDatabase.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=TDB/@EntryIndexedValue">TDB</s:String></wpf:ResourceDictionary>
13 changes: 0 additions & 13 deletions src/TelegramAsDatabase/Constants/TelegramApiConstants.cs

This file was deleted.

12 changes: 0 additions & 12 deletions src/TelegramAsDatabase/Constants/TelegramMessageConstants.cs

This file was deleted.

67 changes: 67 additions & 0 deletions src/TelegramAsDatabase/Contracts/ITDB.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,79 @@ namespace TelegramAsDatabase.Contracts;

public interface ITDB
{
/// <summary>
/// Retrieves a single item of type TDBData<T> from the data store using the provided key.
/// </summary>
/// <typeparam name="T">The type of data being stored and retrieved.</typeparam>
/// <param name="key">The key used to identify the item to retrieve.</param>
/// <param name="cancellationToken">Optional. A token to monitor for cancellation requests.</param>
/// <returns>A task that resolves to a Result<TDBData<T>> representing the retrieved item or an error.</returns>
Task<Result<TDBData<T>>> GetAsync<T>(string key, CancellationToken cancellationToken = default);

/// <summary>
/// Retrieves all keys currently stored in the data store.
/// </summary>
/// <param name="cancellationToken">Optional. A token to monitor for cancellation requests.</param>
/// <returns>A task that resolves to a Result<List<string>> containing all the keys or an error.</returns>
Task<Result<List<string>>> GetAllKeysAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Checks if an item exists in the data store for the specified key.
/// </summary>
/// <typeparam name="T">The type of the data.</typeparam>
/// <param name="key">The key to check for existence.</param>
/// <param name="cancellationToken">Optional. A token to monitor for cancellation requests.</param>
/// <returns>A task that resolves to a Result<bool> indicating whether the item exists (true) or not (false).</returns>
Task<Result<bool>> ExistsAsync<T>(string key, CancellationToken cancellationToken = default);

/// <summary>
/// Saves a single item of type TDBData<T> to the data store.
/// </summary>
/// <typeparam name="T">The type of the data being saved.</typeparam>
/// <param name="item">The item to save to the data store.</param>
/// <param name="cancellationToken">Optional. A token to monitor for cancellation requests.</param>
/// <returns>A task that resolves to a Result indicating whether the save operation was successful or failed.</returns>
Task<Result> SaveAsync<T>(TDBData<T> item, CancellationToken cancellationToken = default);

/// <summary>
/// Saves multiple items of type TDBData<T> to the data store.
/// </summary>
/// <typeparam name="T">The type of the data being saved.</typeparam>
/// <param name="items">The collection of items to save to the data store.</param>
/// <param name="cancellationToken">Optional. A token to monitor for cancellation requests.</param>
/// <returns>A task that resolves to a Result indicating whether the save operation was successful or failed.</returns>
Task<Result> SaveAsync<T>(IEnumerable<TDBData<T>> items, CancellationToken cancellationToken = default);

/// <summary>
/// Updates an existing item in the data store with the provided key and value.
/// </summary>
/// <typeparam name="T">The type of the data being updated.</typeparam>
/// <param name="key">The key of the item to update.</param>
/// <param name="value">The new value to update the item with.</param>
/// <param name="cancellationToken">Optional. A token to monitor for cancellation requests.</param>
/// <returns>A task that resolves to a Result indicating whether the update operation was successful or failed.</returns>
Task<Result> UpdateAsync<T>(string key, TDBData<T> value, CancellationToken cancellationToken = default);

/// <summary>
/// Deletes an item from the data store based on the provided key.
/// </summary>
/// <param name="key">The key of the item to delete.</param>
/// <param name="cancellationToken">Optional. A token to monitor for cancellation requests.</param>
/// <returns>A task that resolves to a Result indicating whether the delete operation was successful or failed.</returns>
Task<Result> DeleteAsync(string key, CancellationToken cancellationToken = default);

/// <summary>
/// Deletes multiple items from the data store based on the provided collection of keys.
/// </summary>
/// <param name="keys">A collection of keys of the items to delete.</param>
/// <param name="cancellationToken">Optional. A token to monitor for cancellation requests.</param>
/// <returns>A task that resolves to a Result indicating whether the delete operation was successful or failed.</returns>
Task<Result> DeleteAsync(IEnumerable<string> keys, CancellationToken cancellationToken = default);

/// <summary>
/// Clears all data from the data store.
/// </summary>
/// <param name="cancellationToken">Optional. A token to monitor for cancellation requests.</param>
/// <returns>A task that resolves to a Result indicating whether the clear operation was successful or failed.</returns>
Task<Result> ClearAsync(CancellationToken cancellationToken = default);
}
76 changes: 75 additions & 1 deletion src/TelegramAsDatabase/Implementations/TDB.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Telegram.Bot;
using Telegram.Bot.Types;
using Telegram.Bot.Types.Enums;
using TelegramAsDatabase.Configs;
using TelegramAsDatabase.Contracts;
using TelegramAsDatabase.Models;

namespace TelegramAsDatabase.Implementations;

public class TDB : ITDB
public class TDB : ITDB, IDisposable
{
private int _indexMessageId;
private readonly TDBConfig _config;
Expand All @@ -23,6 +24,7 @@ public class TDB : ITDB
public TDB(IOptions<TDBConfig> configOptions, [FromKeyedServices(nameof(TDB))] ITelegramBotClient bot, ILogger<TDB> logger)
{
ValidateBotClient(bot);
SetDefaultBotAdminRights(bot);

_bot = bot;
_logger = logger;
Expand All @@ -43,6 +45,17 @@ public TDB(IOptions<TDBConfig> configOptions, [FromKeyedServices(nameof(TDB))] I
});
}

private void SetDefaultBotAdminRights(ITelegramBotClient bot)
{
bot.SetMyDefaultAdministratorRights(new ChatAdministratorRights()
{
CanPostMessages = true,
CanEditMessages = true,
CanDeleteMessages = true,
CanPinMessages = true,
});
}

#region [- Private Methods -]
private void ValidateBotClient(ITelegramBotClient bot)
{
Expand Down Expand Up @@ -73,6 +86,7 @@ private TDBKeyValueIndex GetOrCreateIndex(int messageId = default)

var indexMessage = _bot.SendMessage(_config.ChannelId, tdbIndex, ParseMode.Html).Result;
Task.Run(() => _bot.SetMyDescription(indexMessage.MessageId.ToString()));
Task.Run(() => _bot.PinChatMessage(_config.ChannelId, indexMessage.MessageId));
_indexMessageId = indexMessage.MessageId;

return tdbIndex;
Expand Down Expand Up @@ -108,6 +122,7 @@ public async Task<Result<TDBData<T>>> GetAsync<T>(string key, CancellationToken
}
catch (Exception exception)
{
_logger.LogError(exception.Message);
return Result.Fail(exception.Message);
}
}
Expand All @@ -120,6 +135,7 @@ public async Task<Result<List<string>>> GetAllKeysAsync(CancellationToken cancel
}
catch (Exception exception)
{
_logger.LogError(exception.Message);
return Result.Fail(exception.Message);
}
}
Expand All @@ -132,6 +148,7 @@ public async Task<Result<bool>> ExistsAsync<T>(string key, CancellationToken can
}
catch (Exception exception)
{
_logger.LogError(exception.Message);
return Result.Fail(exception.Message);
}
}
Expand All @@ -140,6 +157,8 @@ public async Task<Result> SaveAsync<T>(TDBData<T> item, CancellationToken cancel
{
try
{
item.Verify();

var message = await _bot.SendMessage(_config.ChannelId, item, ParseMode.Html, cancellationToken: cancellationToken);

_tdbKeyValueIndex.Value.IndexIds.TryAdd(item.Key, message.MessageId);
Expand All @@ -149,6 +168,7 @@ public async Task<Result> SaveAsync<T>(TDBData<T> item, CancellationToken cancel
}
catch (Exception exception)
{
_logger.LogError(exception.Message);
return Result.Fail(exception.Message);
}
}
Expand All @@ -168,6 +188,7 @@ public async Task<Result> SaveAsync<T>(IEnumerable<TDBData<T>> items, Cancellati
}
catch (Exception exception)
{
_logger.LogError(exception.Message);
return Result.Fail(exception.Message);
}
}
Expand All @@ -184,6 +205,7 @@ public async Task<Result> UpdateAsync<T>(string key, TDBData<T> value, Cancellat
}
catch (Exception exception)
{
_logger.LogError(exception.Message);
return Result.Fail(exception.Message);
}
}
Expand All @@ -202,6 +224,32 @@ public async Task<Result> DeleteAsync(string key, CancellationToken cancellation
}
catch (Exception exception)
{
_logger.LogError(exception.Message);
return Result.Fail(exception.Message);
}
}

public async Task<Result> DeleteAsync(IEnumerable<string> keys, CancellationToken cancellationToken = default)
{
try
{
var keysList = keys.ToList();
var messageIds = new List<int>();

foreach (var key in keysList)
{
if (_tdbKeyValueIndex.Value.IndexIds.Remove(key, out var messageId))
messageIds.Add(messageId);
}

Task.Run(async () => await _bot.DeleteMessages(_config.ChannelId, messageIds, cancellationToken), cancellationToken);

UpdateIndex(cancellationToken);
return Result.Ok();
}
catch (Exception exception)
{
_logger.LogError(exception.Message);
return Result.Fail(exception.Message);
}
}
Expand All @@ -210,14 +258,40 @@ public async Task<Result> ClearAsync(CancellationToken cancellationToken = defau
{
try
{
Task.Run(() => _bot.DeleteMessages(_config.ChannelId,
_tdbKeyValueIndex.Value.IndexIds
.Select(x => x.Value)
.ToList()
, cancellationToken), cancellationToken);

_tdbKeyValueIndex.Value.IndexIds.Clear();
await UpdateIndex(cancellationToken);

return Result.Ok();
}
catch (Exception exception)
{
_logger.LogError(exception.Message);
return Result.Fail(exception.Message);
}
}

public void Dispose()
{
try
{
if (_tdbKeyValueIndex.IsValueCreated)
{
UpdateIndex(CancellationToken.None).Wait();
}
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while disposing TDB instance.");
}
finally
{
_logger.LogInformation("The TDB instance was disposed!");
}
}
}
10 changes: 10 additions & 0 deletions src/TelegramAsDatabase/Models/TDBData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ public class TDBData<T>
public string Key { get; set; }
public T Value { get; set; }

public void Verify()
{
if (this == null) throw new ArgumentNullException(nameof(TDBData<T>));
if (string.IsNullOrWhiteSpace(Key)) throw new ArgumentNullException(nameof(Key));
if (Value == null) throw new ArgumentNullException(nameof(Value));

if (TDBConstants.MarkdownCharacters.Any(c => Key.Contains(c)))
throw new ArgumentNullException(nameof(Key));
}

public static implicit operator string(TDBData<T> data) => JsonConvert.SerializeObject(data)!;
public static implicit operator TDBData<T>(string data) => JsonConvert.DeserializeObject<TDBData<T>>(data)!;
}
17 changes: 17 additions & 0 deletions src/TelegramAsDatabase/TDBConstants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TelegramAsDatabase;

internal class TDBConstants
{
internal const int MaxMessageLength = 4096;

internal const int MaxGroupMessagesPerMinute = 20;
internal const int MaxChatMessagesPerSecond = 1;

internal static readonly char[] MarkdownCharacters = ['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'];
}
2 changes: 1 addition & 1 deletion src/TelegramAsDatabase/TelegramAsDatabase.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageIcon>icon.png</PackageIcon>
<RepositoryType>git</RepositoryType>
<PackageTags>Telegram;Database;</PackageTags>
<PackageTags>Telegram;Database;KeyValue;Document;Storage</PackageTags>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
</PropertyGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using FluentResults;
using Microsoft.AspNetCore.Mvc;
using TelegramAsDatabase.Contracts;
using TelegramAsDatabase.Models;
Expand Down Expand Up @@ -54,10 +55,16 @@ public async Task<IActionResult> UpdateUserAsync([FromRoute] string id, [FromBod
return result.IsFailed ? BadRequest(result.Errors) : Ok();
}

[HttpDelete("Users/{id}")]
public async Task<IActionResult> CreateUserAsync([FromRoute] string id, CancellationToken cancellationToken)
[HttpDelete("Users")]
public async Task<IActionResult> CreateUserAsync([FromBody] List<string> ids, CancellationToken cancellationToken)
{
var result = await _tdb.DeleteAsync(id, cancellationToken);
Result result;

if (ids == null || ids.Count == 0)
result = await _tdb.ClearAsync(cancellationToken);
else
result = await _tdb.DeleteAsync(ids, cancellationToken);

return result.IsFailed ? BadRequest(result.Errors) : Ok();
}
}
Expand Down
Loading