diff --git a/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/BotBaseBuilderExtensions.cs b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/BotBaseBuilderExtensions.cs
new file mode 100644
index 0000000..ad0556b
--- /dev/null
+++ b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/BotBaseBuilderExtensions.cs
@@ -0,0 +1,92 @@
+using System;
+using TelegramBotBase.Builder;
+using TelegramBotBase.Builder.Interfaces;
+
+namespace TelegramBotBase.Extensions.Serializer.Database.PostgreSql
+{
+ ///
+ /// Provides extension methods for configuring the use of PostgreSQL Server Database for session serialization.
+ ///
+ public static class BotBaseBuilderExtensions
+ {
+ ///
+ /// Uses an PostgreSQL Server Database to save and restore sessions.
+ ///
+ /// The session serialization stage builder.
+ /// The connection string to the PostgreSQL database.
+ /// The fallback form type.
+ /// The prefix for database table names (default is "tgb_").
+ /// The language selection stage builder.
+ public static ILanguageSelectionStage UsePostgreSqlDatabase(
+ this ISessionSerializationStage builder,
+ string connectionString, Type fallbackForm = null,
+ string tablePrefix = "tgb_")
+ {
+ var serializer = new PostgreSqlSerializer(connectionString, tablePrefix, fallbackForm);
+
+ builder.UseSerialization(serializer);
+
+ return builder as BotBaseBuilder;
+ }
+
+
+ ///
+ /// Uses an PostgreSQL Server Database to save and restore sessions.
+ ///
+ /// The session serialization stage builder.
+ /// The host or IP address of the PostgreSQL server.
+ /// The port number for the PostgreSQL server.
+ /// The name of the PostgreSQL database.
+ /// The user ID for connecting to the PostgreSQL server.
+ /// The password for connecting to the PostgreSQL server.
+ /// The fallback form type.
+ /// The prefix for database table names (default is "tgb_").
+ /// The language selection stage builder.
+ public static ILanguageSelectionStage UsePostgreSqlDatabase(
+ this ISessionSerializationStage builder,
+ string hostOrIp, string port,
+ string databaseName, string userId,
+ string password, Type fallbackForm = null,
+ string tablePrefix = "tgb_")
+ {
+ var connectionString = $"Host={hostOrIp};Port={port};Database={databaseName};Username={userId};Password={password}";
+
+ var serializer = new PostgreSqlSerializer(connectionString, tablePrefix, fallbackForm);
+
+ builder.UseSerialization(serializer);
+
+ return builder as BotBaseBuilder;
+ }
+
+ ///
+ /// Uses an PostgreSQL Server Database with Windows Authentication to save and restore sessions.
+ ///
+ /// The session serialization stage builder.
+ /// The host or IP address of the PostgreSQL server.
+ /// The port number for the PostgreSQL server.
+ /// The name of the PostgreSQL database.
+ /// A flag indicating whether to use Windows Authentication (true) or not (false).
+ /// The fallback form type.
+ /// The prefix for database table names (default is "tgb_").
+ /// The language selection stage builder.
+ public static ILanguageSelectionStage UsePostgreSqlDatabase(
+ this ISessionSerializationStage builder,
+ string hostOrIp, string port,
+ string databaseName, bool integratedSecurity = true,
+ Type fallbackForm = null, string tablePrefix = "tgb_")
+ {
+ if (!integratedSecurity)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ var connectionString = $"Host={hostOrIp};Port={port};Database={databaseName};Integrated Security=true;";
+
+ var serializer = new PostgreSqlSerializer(connectionString, tablePrefix, fallbackForm);
+
+ builder.UseSerialization(serializer);
+
+ return builder as BotBaseBuilder;
+ }
+ }
+}
diff --git a/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/PostgreSqlSerializer.cs b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/PostgreSqlSerializer.cs
new file mode 100644
index 0000000..7875b70
--- /dev/null
+++ b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/PostgreSqlSerializer.cs
@@ -0,0 +1,252 @@
+using Npgsql;
+using System;
+using System.Data;
+using NpgsqlTypes;
+using TelegramBotBase.Args;
+using TelegramBotBase.Base;
+using TelegramBotBase.Form;
+using TelegramBotBase.Interfaces;
+
+namespace TelegramBotBase.Extensions.Serializer.Database.PostgreSql
+{
+ ///
+ /// Represents a PostgreSQL implementation of the for saving and loading form states.
+ ///
+ public class PostgreSqlSerializer : IStateMachine
+ {
+ private readonly string insertIntoSessionSql;
+ private readonly string insertIntoSessionsDataSql;
+ private readonly string selectAllDevicesSessionsSql;
+ private readonly string selectAllDevicesSessionsDataSql;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The connection string to the PostgreSQL database.
+ /// The prefix for database table names (default is "tgb_").
+ /// The fallback state form type.
+ /// Thrown when is null.
+ /// Thrown when is not a subclass of .
+ public PostgreSqlSerializer(string connectionString, string tablePrefix = "tgb_", Type fallbackStateForm = null)
+ {
+ ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
+
+ TablePrefix = tablePrefix;
+
+ FallbackStateForm = fallbackStateForm;
+
+ if (FallbackStateForm != null && !FallbackStateForm.IsSubclassOf(typeof(FormBase)))
+ {
+ throw new ArgumentException($"{nameof(FallbackStateForm)} is not a subclass of {nameof(FormBase)}");
+ }
+
+ insertIntoSessionSql = "INSERT INTO " + TablePrefix +
+ "devices_sessions (deviceId, deviceTitle, \"FormUri\", \"QualifiedName\") VALUES (@deviceId, @deviceTitle, @FormUri, @QualifiedName)";
+ insertIntoSessionsDataSql = "INSERT INTO " + TablePrefix + "devices_sessions_data (deviceId, key, value, type) VALUES (@deviceId, @key, @value, @type)";
+
+ selectAllDevicesSessionsSql = "SELECT * FROM " + TablePrefix + "devices_sessions";
+ selectAllDevicesSessionsDataSql = "SELECT * FROM " + TablePrefix + "devices_sessions_data WHERE deviceId = @deviceId";
+ }
+
+ ///
+ /// Gets the connection string to the PostgreSQL database.
+ ///
+ public string ConnectionString { get; }
+
+ ///
+ /// Gets or sets the table name prefix for database tables.
+ ///
+ public string TablePrefix { get; set; }
+
+ ///
+ /// Gets or sets the fallback state form type.
+ ///
+ public Type FallbackStateForm { get; set; }
+
+ ///
+ ///
+ /// Saves form states to the PostgreSQL database.
+ ///
+ /// The containing the states to be saved.
+ public void SaveFormStates(SaveStatesEventArgs e)
+ {
+ var container = e.States;
+
+ //Cleanup old Session data
+ Cleanup();
+
+ using (var connection = new NpgsqlConnection(ConnectionString))
+ {
+ connection.Open();
+
+ //Store session data in database
+ foreach (var state in container.States)
+ {
+ using (var sessionCommand = connection.CreateCommand())
+ {
+ sessionCommand.CommandText = insertIntoSessionSql;
+
+ sessionCommand.Parameters.Add(new NpgsqlParameter("@deviceId", NpgsqlDbType.Bigint){Value = state.DeviceId });
+ sessionCommand.Parameters.Add(new NpgsqlParameter("@deviceTitle", DbType.StringFixedLength){Value = state.ChatTitle ?? string.Empty});
+ sessionCommand.Parameters.Add(new NpgsqlParameter("@FormUri", DbType.StringFixedLength) {Value = state.FormUri});
+ sessionCommand.Parameters.Add(new NpgsqlParameter("@QualifiedName", DbType.StringFixedLength){Value = state.QualifiedName });
+
+ sessionCommand.ExecuteNonQuery();
+ }
+ }
+ }
+
+ using (var connection = new NpgsqlConnection(ConnectionString))
+ {
+ connection.Open();
+
+ foreach (var state in container.States)
+ {
+ SaveSessionsData(state, connection);
+ }
+ }
+ }
+
+ ///
+ ///
+ /// Loads form states from the PostgreSQL database.
+ ///
+ /// A containing the loaded form states.
+ public StateContainer LoadFormStates()
+ {
+ var stateContainer = new StateContainer();
+
+ using (var connection = new NpgsqlConnection(ConnectionString))
+ {
+ connection.Open();
+
+ using (var sessionCommand = connection.CreateCommand())
+ {
+ sessionCommand.CommandText = selectAllDevicesSessionsSql;
+
+ var sessionTable = new DataTable();
+ using (var dataAdapter = new NpgsqlDataAdapter(sessionCommand))
+ {
+ dataAdapter.Fill(sessionTable);
+
+ foreach (DataRow row in sessionTable.Rows)
+ {
+ var stateEntry = new StateEntry
+ {
+ DeviceId = (long)row["deviceId"],
+ ChatTitle = row["deviceTitle"].ToString(),
+ FormUri = row["FormUri"].ToString(),
+ QualifiedName = row["QualifiedName"].ToString()
+ };
+
+ stateContainer.States.Add(stateEntry);
+
+ if (stateEntry.DeviceId > 0)
+ {
+ stateContainer.ChatIds.Add(stateEntry.DeviceId);
+ }
+ else
+ {
+ stateContainer.GroupIds.Add(stateEntry.DeviceId);
+ }
+
+ LoadDataTable(connection, row, stateEntry);
+ }
+ }
+ }
+ }
+
+ return stateContainer;
+ }
+
+ ///
+ /// Cleans up old session data in the PostgreSQL database.
+ ///
+ private void Cleanup()
+ {
+ using (var connection = new NpgsqlConnection(ConnectionString))
+ {
+ connection.Open();
+
+ using (var clearCommand = connection.CreateCommand())
+ {
+ clearCommand.CommandText = $"DELETE FROM {TablePrefix}devices_sessions_data";
+ clearCommand.ExecuteNonQuery();
+ }
+
+ using (var clearCommand = connection.CreateCommand())
+ {
+ clearCommand.CommandText = $"DELETE FROM {TablePrefix}devices_sessions";
+ clearCommand.ExecuteNonQuery();
+ }
+ }
+ }
+
+ ///
+ /// Saves session data to the PostgreSQL database.
+ ///
+ /// The state entry containing session data to be saved.
+ /// The NpgsqlConnection used for the database interaction.
+ private void SaveSessionsData(StateEntry state, NpgsqlConnection connection)
+ {
+ foreach (var data in state.Values)
+ {
+ using (var dataCommand = connection.CreateCommand())
+ {
+ dataCommand.CommandText = insertIntoSessionsDataSql;
+
+ dataCommand.Parameters.Add(new NpgsqlParameter("@deviceId", NpgsqlDbType.Bigint) { Value = state.DeviceId });
+ dataCommand.Parameters.Add(new NpgsqlParameter("@key", DbType.StringFixedLength) { Value = data.Key });
+
+ var type = data.Value.GetType();
+
+ if (type.IsPrimitive || type == typeof(string))
+ {
+ dataCommand.Parameters.Add(new NpgsqlParameter("@value", NpgsqlDbType.Text) { Value = data.Value });
+ }
+ else
+ {
+ var json = System.Text.Json.JsonSerializer.Serialize(data.Value);
+ dataCommand.Parameters.Add(new NpgsqlParameter("@value", NpgsqlDbType.Text) { Value = json });
+ }
+
+ dataCommand.Parameters.Add(new NpgsqlParameter("@type", DbType.StringFixedLength) { Value = type.AssemblyQualifiedName });
+
+ dataCommand.ExecuteNonQuery();
+ }
+ }
+ }
+
+ ///
+ /// Loads session data from the PostgreSQL database.
+ ///
+ /// The NpgsqlConnection used for the database interaction.
+ /// The DataRow representing a session entry in the main sessions table.
+ /// The StateEntry object to which session data will be loaded.
+ private void LoadDataTable(NpgsqlConnection connection, DataRow row, StateEntry stateEntry)
+ {
+ using (var sessionCommand = connection.CreateCommand())
+ {
+ sessionCommand.CommandText = selectAllDevicesSessionsDataSql;
+ sessionCommand.Parameters.Add(new NpgsqlParameter("@deviceId", row["deviceId"]));
+
+ var dataCommandTable = new DataTable();
+ using (var npgSqlDataAdapter = new NpgsqlDataAdapter(sessionCommand))
+ {
+ npgSqlDataAdapter.Fill(dataCommandTable);
+
+ foreach (DataRow dataRow in dataCommandTable.Rows)
+ {
+ var key = dataRow["key"].ToString();
+ var type = Type.GetType(dataRow["type"].ToString());
+
+ var value = System.Text.Json.JsonSerializer.Deserialize(dataRow["value"].ToString(), type);
+
+ stateEntry.Values.Add(key, value);
+ }
+ }
+
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/README.md b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/README.md
new file mode 100644
index 0000000..1f1377f
--- /dev/null
+++ b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/README.md
@@ -0,0 +1,27 @@
+# TelegramBotBase.Extensions.Serializer.Database.PostgreSQL
+
+[](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.PostgreSQL/)
+[](https://www.t.me/tgbotbase)
+
+[](https://raw.githubusercontent.com/MajMcCloud/TelegramBotFramework/master/LICENCE.md)
+[](https://www.nuget.org/packages/TelegramBotBase.Extensions.Serializer.Database.PostgreSQL)
+
+## How to use
+
+```csharp
+using TelegramBotBase.Extensions.Serializer.Database.PostgreSQL;
+
+
+var bot = BotBaseBuilder
+ .Create()
+ .WithAPIKey(APIKey)
+ .DefaultMessageLoop()
+ .WithStartForm()
+ .NoProxy()
+ .OnlyStart()
+ .UseSQLDatabase("localhost", "telegram_bot")
+ .UseEnglish()
+ .Build();
+
+bot.Start();
+```
diff --git a/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/TelegramBotBase.Extensions.Serializer.Database.PostgreSql.csproj b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/TelegramBotBase.Extensions.Serializer.Database.PostgreSql.csproj
new file mode 100644
index 0000000..7103081
--- /dev/null
+++ b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/TelegramBotBase.Extensions.Serializer.Database.PostgreSql.csproj
@@ -0,0 +1,29 @@
+
+
+
+ netstandard2.0;netcoreapp3.1;net8
+ True
+ https://github.com/MajMcCloud/TelegramBotFramework
+ https://github.com/MajMcCloud/TelegramBotFramework
+ MIT
+ true
+ snupkg
+ 1.0.1
+ 1.0.1
+ 1.0.1
+
+ A session serializer for PostgreSQL Server.
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
diff --git a/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/create_tables.sql b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/create_tables.sql
new file mode 100644
index 0000000..d83bd38
--- /dev/null
+++ b/TelegramBotBase.Extensions.Serializer.Database.PostgreSql/create_tables.sql
@@ -0,0 +1,25 @@
+-- Enable uuid-ossp extension
+CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
+
+-- Create table tgb_devices_sessions
+CREATE TABLE tgb_devices_sessions (
+ deviceId bigint NOT NULL,
+ deviceTitle character varying(512) NOT NULL,
+ "FormUri" character varying(512) NOT NULL,
+ "QualifiedName" character varying(512) NOT NULL,
+ CONSTRAINT PK_tgb_devices_sessions_1 PRIMARY KEY (deviceId)
+);
+
+-- Create table tgb_devices_sessions_data
+CREATE TABLE tgb_devices_sessions_data (
+ Id uuid DEFAULT uuid_generate_v4() NOT NULL,
+ deviceId bigint NOT NULL,
+ key character varying(512) NOT NULL,
+ "value" text NOT NULL,
+ "type" character varying(512) NOT NULL,
+ CONSTRAINT PK_tgb_devices_session_data PRIMARY KEY (Id)
+);
+
+-- Add default constraint for Id column in tgb_devices_sessions_data
+ALTER TABLE tgb_devices_sessions_data
+ ALTER COLUMN Id SET DEFAULT uuid_generate_v4();