This commit is contained in:
2026-01-16 15:23:03 +02:00
commit b4fb35486c
89 changed files with 76576 additions and 0 deletions

25
.dockerignore Normal file
View File

@@ -0,0 +1,25 @@
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md

14
.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
/.idea
/.vscode
/database_data/
/RepoProvider/bin
/RepoProvider/obj
/SethRepository/bin
/SethRepository/obj
/RepositoryManager/bin
/RepositoryManager/obj
**.dll
**.pdb
**.DS_Store
**.vs
**.suo

View File

@@ -0,0 +1,17 @@
namespace RepoProvider.Database;
public class DatabaseConnectionDetails : IDatabaseConnectionDetails
{
public string DataSource { get; }
public string DatabaseName { get; }
public string Username { get; }
public string Password { get; }
public DatabaseConnectionDetails(string dataSource, string databaseName, string username, string password)
{
DataSource = dataSource;
DatabaseName = databaseName;
Username = username;
Password = password;
}
}

View File

@@ -0,0 +1,9 @@
namespace RepoProvider.Database;
public interface IDatabaseConnectionDetails
{
public string DataSource { get; }
public string DatabaseName { get; }
public string Username { get; }
public string Password { get; }
}

View File

@@ -0,0 +1,171 @@
namespace RepoProvider.Database;
public interface IDbManager
{
/// <summary>
/// Open the SQL Connection. To close use the Stop() method
/// </summary>
/// <returns></returns>
Task Open();
/// <summary>
/// Stop the connection to the SQL Database
/// </summary>
/// <returns></returns>
void Stop();
/// <summary>
/// <para>
/// Insert into a specified table some values
/// </para>
/// </summary>
/// <param name="tableName">The table name</param>
/// <param name="values">The values to be inserted</param>
/// <returns></returns>
Task<bool> InsertAsync(string tableName, params KeyValuePair<string, object>[] values);
/// <summary>
/// <para>
/// Insert into a specified table some values
/// </para>
/// </summary>
/// <param name="tableName">The table name</param>
/// <param name="outputColumn">The column from which the value should be returned after the insertion</param>
/// <param name="values">The values to be inserted (in the correct order and number)</param>
/// <returns>The value of the specified column after the insertion. It is usually the Id. If null, nothing is returned (failed probably at insertion) </returns>
Task<T?> InsertAsyncWithReturn<T>(string tableName, string outputColumn, params KeyValuePair<string, object>[] values);
/// <summary>
/// <para>
/// Insert into a specified table some values
/// </para>
/// </summary>
/// <param name="tableName">The table name</param>
/// <param name="values">The values to be inserted (in the correct order and number)</param>
/// <returns></returns>
bool Insert(string tableName, params KeyValuePair<string, object>[] values);
Task<bool> UpdateAsync(string tableName, KeyValuePair<string, object> matchKey, params KeyValuePair<string, object>[] values);
bool Update(string tableName, KeyValuePair<string, object> matchKey, params KeyValuePair<string, object>[] values);
/// <summary>
/// Remove every row in a table that has any specified properties
/// </summary>
/// <param name="tableName">The table name</param>
/// <param name="properties">The properties to filter the deletion</param>
/// <returns></returns>
Task<bool> DeleteAsync(string tableName, params KeyValuePair<string, object>[] properties);
/// <summary>
/// Remove every row in a table that has any specified properties
/// </summary>
/// <param name="tableName">The table name</param>
/// <param name="properties">The properties to filter the deletion</param>
/// <returns></returns>
bool Delete(string tableName, params KeyValuePair<string, object>[] properties);
/// <summary>
/// Check if the key exists in the table
/// </summary>
/// <param name="tableName">The table name</param>
/// <param name="properties">The properties of the search</param>
/// <returns></returns>
Task<bool> ContainsAnyAsync(string tableName, params KeyValuePair<string, object>[] properties);
/// <summary>
/// Check if the key exists in the table
/// </summary>
/// <param name="tableName">The table name</param>
/// <param name="properties">The properties of the search</param>
/// <returns></returns>
bool ContainsAny(string tableName, params KeyValuePair<string, object>[] properties);
/// <summary>
/// Execute a custom query
/// </summary>
/// <param name="query">The query</param>
/// <returns>The number of rows that the query modified</returns>
Task<int> ExecuteAsync(string query);
/// <summary>
/// Execute a custom query
/// </summary>
/// <param name="query">The query</param>
/// <returns>The number of rows that the query modified</returns>
int Execute(string query);
/// <summary>
/// Read data from the result table and return the first row
/// </summary>
/// <param name="query">The query</param>
/// <returns>The result is a string that has all values separated by space character</returns>
Task<string?> ReadDataAsync(string query);
/// <summary>
/// Read data from the result table and return the first row
/// </summary>
/// <param name="query">The query</param>
/// <param name="parameters">The parameters of the query</param>
/// <returns>The result is a string that has all values separated by space character</returns>
Task<string?> ReadDataAsync(string query, params KeyValuePair<string, object>[] parameters);
/// <summary>
/// Read data from the result table and return the first row
/// </summary>
/// <param name="query">The query</param>
/// <returns>The result is a string that has all values separated by space character</returns>
string? ReadData(string query);
/// <summary>
/// Read data from the result table and return the first row
/// </summary>
/// <param name="query">The query</param>
/// <returns>The first row as separated items</returns>
Task<object[]?> ReadDataArrayAsync(string query);
Task<object[]?> ReadDataArrayAsync(string query, params KeyValuePair<string, object>[] parameters);
/// <summary>
/// Read data from the result table and return the first row
/// </summary>
/// <param name="query">The query</param>
/// <returns>The first row as separated items</returns>
object[]? ReadDataArray(string query);
/// <summary>
/// Read all rows from the result table and return them as a list of string arrays. The string arrays contain the
/// values of each row
/// </summary>
/// <param name="query">The query</param>
/// <returns>A list of string arrays representing the values that the query returns</returns>
Task<List<string[]>?> ReadAllRowsAsync(string query);
/// <summary>
/// Execute a query with parameters
/// </summary>
/// <param name="query">The query to execute</param>
/// <param name="parameters">The parameters of the query</param>
/// <returns>The number of rows that the query modified in the database</returns>
Task<int> ExecuteNonQueryAsync(string query, params KeyValuePair<string, object>[] parameters);
/// <summary>
/// Execute a query with parameters that returns a specific type of object. The function will return the first row of the result transformed into the specified type.
/// </summary>
/// <param name="query">The query to execute</param>
/// <param name="convertor">The convertor function that will convert each row of the response into an object of <typeparamref name="T"/></param>
/// <param name="parameters">The parameters of the query</param>
/// <typeparam name="T">The return object type</typeparam>
/// <returns>An object of type T that represents the output of the convertor function based on the array of objects that the first row of the result has</returns>
Task<T?> ReadObjectOfTypeAsync<T>(string query, Func<object[], T> convertor, params KeyValuePair<string, object>[] parameters);
/// <summary>
/// Execute a query with parameters that returns a specific type of object. The function will return a list of objects of the specified type.
/// </summary>
/// <param name="query">The query to execute</param>
/// <param name="convertor">The convertor from object[] to T</param>
/// <param name="parameters">The parameters of the query</param>
/// <typeparam name="T">The expected object type</typeparam>
/// <returns>A list of objects of type T that represents each line of the output of the specified query, converted to T</returns>
Task<List<T>> ReadListOfTypeAsync<T>(string query, Func<object[], T> convertor,
params KeyValuePair<string, object>[] parameters);
}

View File

@@ -0,0 +1,386 @@
namespace RepoProvider.Database;
using System.Data;
using Npgsql;
/// <summary>
/// PostgreSQL implementation of <see cref="IDbManager"/>.
/// Usage is identical to <c>SqlServerDbManager</c>; the only difference is the
/// underlying provider (Npgsql) and PostgreSQL-specific SQL syntax.
/// </summary>
public class PostgresDbManager : IDbManager
{
private readonly NpgsqlConnection _connection;
public PostgresDbManager(IDatabaseConnectionDetails connectionDetails)
{
// Example: Host=localhost;Port=5432;Database=mydb;Username=user;Password=pass;
var connectionString =
$"Host={connectionDetails.DataSource};Database={connectionDetails.DatabaseName};Username={connectionDetails.Username};Password={connectionDetails.Password};";
_connection = new NpgsqlConnection(connectionString);
}
/* --------------------------------------------------------------------
* Generic helpers
* ------------------------------------------------------------------*/
private static NpgsqlParameter? CreateParameter(string name, object value)
{
var parameter = new NpgsqlParameter
{
ParameterName = name,
Value = value
};
// Map common CLR types to DbType (works fine with NpgsqlParameter)
// ─ you can extend this switch if you need more cases.
if (value is string) parameter.DbType = DbType.String;
else if (value is int) parameter.DbType = DbType.Int32;
else if (value is long) parameter.DbType = DbType.Int64;
else if (value is float) parameter.DbType = DbType.Single;
else if (value is double) parameter.DbType = DbType.Double;
else if (value is bool) parameter.DbType = DbType.Boolean;
else if (value is DateTime) parameter.DbType = DbType.DateTime;
else if (value is byte[]) parameter.DbType = DbType.Binary;
else if (value is Guid) parameter.DbType = DbType.Guid;
else if (value is decimal) parameter.DbType = DbType.Decimal;
else if (value is TimeSpan) parameter.DbType = DbType.Time;
else if (value is DateTimeOffset) parameter.DbType = DbType.DateTimeOffset;
else if (value is ushort) parameter.DbType = DbType.UInt16;
else if (value is uint) parameter.DbType = DbType.UInt32;
else if (value is ulong) parameter.DbType = DbType.UInt64;
else if (value is sbyte) parameter.DbType = DbType.SByte;
else if (value is short) parameter.DbType = DbType.Int16;
else if (value is byte) parameter.DbType = DbType.Byte;
else if (value is char) parameter.DbType = DbType.StringFixedLength;
else if (value is char[]) parameter.DbType = DbType.StringFixedLength;
else return null;
return parameter;
}
private static NpgsqlParameter? CreateParameter(KeyValuePair<string, object> kvp)
=> CreateParameter(kvp.Key, kvp.Value);
/* --------------------------------------------------------------------
* Connection control
* ------------------------------------------------------------------*/
public async Task Open() => await _connection.OpenAsync();
public async void Stop() => await _connection.CloseAsync();
/* --------------------------------------------------------------------
* Inserts
* ------------------------------------------------------------------*/
public async Task<bool> InsertAsync(string tableName, params KeyValuePair<string, object>[] values)
{
string cols = string.Join(", ", values.Select(v => v.Key));
string parms = string.Join(", ", values.Select(v => $"@{v.Key}"));
string query = $"INSERT INTO {tableName} ({cols}) VALUES ({parms})";
await using var cmd = new NpgsqlCommand(query, _connection);
foreach (var v in values) cmd.Parameters.Add(CreateParameter(v)!);
return await cmd.ExecuteNonQueryAsync() > 0;
}
public bool Insert(string tableName, params KeyValuePair<string, object>[] values)
{
string cols = string.Join(", ", values.Select(v => v.Key));
string parms = string.Join(", ", values.Select(v => $"@{v.Key}"));
string query = $"INSERT INTO {tableName} ({cols}) VALUES ({parms})";
using var cmd = new NpgsqlCommand(query, _connection);
foreach (var v in values) cmd.Parameters.Add(CreateParameter(v)!);
return cmd.ExecuteNonQuery() > 0;
}
public async Task<T?> InsertAsyncWithReturn<T>(
string tableName, string outputColumn, params KeyValuePair<string, object>[] values)
{
string cols = string.Join(", ", values.Select(v => v.Key));
string parms = string.Join(", ", values.Select(v => $"@{v.Key}"));
string query = $"INSERT INTO {tableName} ({cols}) VALUES ({parms}) RETURNING {outputColumn}";
await using var cmd = new NpgsqlCommand(query, _connection);
foreach (var v in values) cmd.Parameters.Add(CreateParameter(v)!);
object? result = await cmd.ExecuteScalarAsync();
return result == null ? default : (T)result;
}
/* --------------------------------------------------------------------
* Updates
* ------------------------------------------------------------------*/
public async Task<bool> UpdateAsync(
string tableName, KeyValuePair<string, object> matchKey, params KeyValuePair<string, object>[] values)
{
string set = string.Join(", ", values.Select(v => $"{v.Key} = @{v.Key}"));
string query = $"UPDATE {tableName} SET {set} WHERE {matchKey.Key} = @{matchKey.Key}";
await using var cmd = new NpgsqlCommand(query, _connection);
foreach (var v in values) cmd.Parameters.Add(CreateParameter(v)!);
cmd.Parameters.Add(CreateParameter(matchKey)!);
return await cmd.ExecuteNonQueryAsync() > 0;
}
public bool Update(
string tableName, KeyValuePair<string, object> matchKey, params KeyValuePair<string, object>[] values)
{
string set = string.Join(", ", values.Select(v => $"{v.Key} = @{v.Key}"));
string query = $"UPDATE {tableName} SET {set} WHERE {matchKey.Key} = @{matchKey.Key}";
using var cmd = new NpgsqlCommand(query, _connection);
foreach (var v in values) cmd.Parameters.Add(CreateParameter(v)!);
cmd.Parameters.Add(CreateParameter(matchKey)!);
return cmd.ExecuteNonQuery() > 0;
}
/* --------------------------------------------------------------------
* Deletes
* ------------------------------------------------------------------*/
public async Task<bool> DeleteAsync(string tableName, params KeyValuePair<string, object>[] properties)
{
string where = string.Join(" AND ", properties.Select(p => $"{p.Key} = @{p.Key}"));
string query = $"DELETE FROM {tableName} WHERE {where}";
await using var cmd = new NpgsqlCommand(query, _connection);
foreach (var p in properties) cmd.Parameters.Add(CreateParameter(p)!);
return await cmd.ExecuteNonQueryAsync() > 0;
}
public bool Delete(string tableName, params KeyValuePair<string, object>[] properties)
{
string where = string.Join(" AND ", properties.Select(p => $"{p.Key} = @{p.Key}"));
string query = $"DELETE FROM {tableName} WHERE {where}";
using var cmd = new NpgsqlCommand(query, _connection);
foreach (var p in properties) cmd.Parameters.Add(CreateParameter(p)!);
return cmd.ExecuteNonQuery() > 0;
}
/* --------------------------------------------------------------------
* Exists / Contains
* ------------------------------------------------------------------*/
public async Task<bool> ContainsAnyAsync(string tableName, params KeyValuePair<string, object>[] properties)
{
string where = string.Join(" AND ", properties.Select(p => $"{p.Key} = @{p.Key}"));
string query = $"SELECT COUNT(*) FROM {tableName} WHERE {where}";
await using var cmd = new NpgsqlCommand(query, _connection);
foreach (var p in properties) cmd.Parameters.Add(CreateParameter(p)!);
object? count = await cmd.ExecuteScalarAsync();
return count != null && Convert.ToInt64(count) > 0;
}
public bool ContainsAny(string tableName, params KeyValuePair<string, object>[] properties)
{
string where = string.Join(" AND ", properties.Select(p => $"{p.Key} = @{p.Key}"));
string query = $"SELECT COUNT(*) FROM {tableName} WHERE {where}";
using var cmd = new NpgsqlCommand(query, _connection);
foreach (var p in properties) cmd.Parameters.Add(CreateParameter(p)!);
object? count = cmd.ExecuteScalar();
return count != null && Convert.ToInt64(count) > 0;
}
/* --------------------------------------------------------------------
* Arbitrary execution helpers
* ------------------------------------------------------------------*/
public async Task<int> ExecuteAsync(string query)
{
if (_connection.State != ConnectionState.Open)
await _connection.OpenAsync();
await using var cmd = new NpgsqlCommand(query, _connection);
return await cmd.ExecuteNonQueryAsync();
}
public int Execute(string query)
{
if (_connection.State != ConnectionState.Open)
_connection.Open();
using var cmd = new NpgsqlCommand(query, _connection);
return cmd.ExecuteNonQuery();
}
public async Task<int> ExecuteNonQueryAsync(string query, params KeyValuePair<string, object>[] parameters)
{
if (_connection.State != ConnectionState.Open)
await _connection.OpenAsync();
await using var cmd = new NpgsqlCommand(query, _connection);
foreach (var p in parameters) cmd.Parameters.Add(CreateParameter(p)!);
return await cmd.ExecuteNonQueryAsync();
}
/* --------------------------------------------------------------------
* Read helpers (string / array / list)
* ------------------------------------------------------------------*/
public async Task<string?> ReadDataAsync(string query)
{
if (_connection.State != ConnectionState.Open)
await _connection.OpenAsync();
await using var cmd = new NpgsqlCommand(query, _connection);
await using var reader = await cmd.ExecuteReaderAsync();
if (!reader.Read()) return null;
var values = new object[reader.FieldCount];
reader.GetValues(values);
return string.Join(" ", values);
}
public async Task<string?> ReadDataAsync(string query, params KeyValuePair<string, object>[] parameters)
{
if (_connection.State != ConnectionState.Open)
await _connection.OpenAsync();
await using var cmd = new NpgsqlCommand(query, _connection);
foreach (var p in parameters) cmd.Parameters.Add(CreateParameter(p)!);
await using var reader = await cmd.ExecuteReaderAsync();
if (!reader.Read()) return null;
var values = new object[reader.FieldCount];
reader.GetValues(values);
return string.Join(" ", values);
}
public string? ReadData(string query)
{
if (_connection.State != ConnectionState.Open)
_connection.Open();
using var cmd = new NpgsqlCommand(query, _connection);
using var reader = cmd.ExecuteReader();
if (!reader.Read()) return null;
var values = new object[reader.FieldCount];
reader.GetValues(values);
return string.Join(" ", values);
}
public async Task<object[]?> ReadDataArrayAsync(string query)
{
if (_connection.State != ConnectionState.Open)
await _connection.OpenAsync();
await using var cmd = new NpgsqlCommand(query, _connection);
await using var reader = await cmd.ExecuteReaderAsync();
if (!reader.Read()) return null;
var values = new object[reader.FieldCount];
reader.GetValues(values);
return values;
}
public async Task<object[]?> ReadDataArrayAsync(string query, params KeyValuePair<string, object>[] parameters)
{
if (_connection.State != ConnectionState.Open)
await _connection.OpenAsync();
await using var cmd = new NpgsqlCommand(query, _connection);
foreach (var p in parameters) cmd.Parameters.Add(CreateParameter(p)!);
await using var reader = await cmd.ExecuteReaderAsync();
if (!reader.Read()) return null;
var values = new object[reader.FieldCount];
reader.GetValues(values);
return values;
}
public object[]? ReadDataArray(string query)
{
if (_connection.State != ConnectionState.Open)
_connection.Open();
using var cmd = new NpgsqlCommand(query, _connection);
using var reader = cmd.ExecuteReader();
if (!reader.Read()) return null;
var values = new object[reader.FieldCount];
reader.GetValues(values);
return values;
}
public async Task<List<string[]>?> ReadAllRowsAsync(string query)
{
if (_connection.State != ConnectionState.Open)
await _connection.OpenAsync();
await using var cmd = new NpgsqlCommand(query, _connection);
await using var reader = await cmd.ExecuteReaderAsync();
if (!reader.HasRows) return null;
List<string[]> rows = new();
while (await reader.ReadAsync())
{
var values = new string[reader.FieldCount];
reader.GetValues(values);
rows.Add(values);
}
return rows.Count == 0 ? null : rows;
}
public async Task<T?> ReadObjectOfTypeAsync<T>(
string query, Func<object[], T> convertor, params KeyValuePair<string, object>[] parameters)
{
if (_connection.State != ConnectionState.Open)
await _connection.OpenAsync();
await using var cmd = new NpgsqlCommand(query, _connection);
foreach (var p in parameters) cmd.Parameters.Add(CreateParameter(p)!);
await using var reader = await cmd.ExecuteReaderAsync();
if (!reader.Read()) return default;
var values = new object[reader.FieldCount];
reader.GetValues(values);
return convertor(values);
}
public async Task<List<T>> ReadListOfTypeAsync<T>(
string query, Func<object[], T> convertor, params KeyValuePair<string, object>[] parameters)
{
if (_connection.State != ConnectionState.Open)
await _connection.OpenAsync();
await using var cmd = new NpgsqlCommand(query, _connection);
foreach (var p in parameters) cmd.Parameters.Add(CreateParameter(p)!);
await using var reader = await cmd.ExecuteReaderAsync();
if (!reader.HasRows) return new();
List<T> rows = new();
while (await reader.ReadAsync())
{
var values = new object[reader.FieldCount];
reader.GetValues(values);
rows.Add(convertor(values));
}
return rows;
}
}

View File

@@ -0,0 +1,595 @@
using System.Data;
using Microsoft.Data.SqlClient;
namespace RepoProvider.Database;
public class SqlServerDbManager : IDbManager
{
private readonly SqlConnection _connection;
public SqlServerDbManager(IDatabaseConnectionDetails connectionDetails)
{
var connectionString = $"Data Source={connectionDetails.DataSource};Initial Catalog={connectionDetails.DatabaseName};User ID={connectionDetails.Username};Password={connectionDetails.Password};MultipleActiveResultSets=True;TrustServerCertificate=True;";
_connection = new SqlConnection(connectionString);
}
public async Task Open()
{
await _connection.OpenAsync();
}
public async Task<bool> InsertAsync(string tableName, params KeyValuePair<string, object>[] values)
{
string columns = string.Join(", ", values.Select(v => v.Key));
string parameters = string.Join(", ", values.Select(v => $"@{v.Key}"));
var query = $"INSERT INTO {tableName} ({columns}) VALUES ({parameters})";
var command = new SqlCommand(query, _connection);
foreach (var parameter in values)
{
var p = CreateParameter(parameter);
if (p is not null)
{
command.Parameters.Add(p);
}
}
int rowsAffected = await command.ExecuteNonQueryAsync();
return rowsAffected > 0;
}
public async Task<T?> InsertAsyncWithReturn<T>(string tableName, string outputColumn, params KeyValuePair<string, object>[] values)
{
string columns = string.Join(", ", values.Select(v => v.Key));
string parameters = string.Join(", ", values.Select(v => $"@{v.Key}"));
var query = $"INSERT INTO {tableName} ({columns}) output inserted.{outputColumn} VALUES ({parameters})";
var command = new SqlCommand(query, _connection);
foreach (var parameter in values)
{
var p = CreateParameter(parameter);
if (p is not null)
{
command.Parameters.Add(p);
}
}
var result = await command.ExecuteScalarAsync();
if (result is null)
{
return default;
}
return (T)result;
}
public bool Insert(string tableName, params KeyValuePair<string, object>[] values)
{
string columns = string.Join(", ", values.Select(v => v.Key));
string parameters = string.Join(", ", values.Select(v => $"@{v.Key}"));
string query = $"INSERT INTO {tableName} ({columns}) VALUES ({parameters})";
var command = new SqlCommand(query, _connection);
foreach (var parameter in values)
{
var p = CreateParameter(parameter);
if (p is not null)
{
command.Parameters.Add(p);
}
}
int rowsAffected = command.ExecuteNonQuery();
return rowsAffected > 0;
}
public async Task<bool> UpdateAsync(string tableName, KeyValuePair<string, object> matchKey, params KeyValuePair<string, object>[] values)
{
string setValues = string.Join(", ", values.Select(v => $"{v.Key} = @{v.Key}"));
string query = $"UPDATE {tableName} SET {setValues} WHERE {matchKey.Key} = @{matchKey.Key}";
var command = new SqlCommand(query, _connection);
foreach (var parameter in values)
{
var p = CreateParameter(parameter);
if (p is not null)
{
command.Parameters.Add(p);
}
}
command.Parameters.Add(CreateParameter(matchKey));
int rowsAffected = await command.ExecuteNonQueryAsync();
return rowsAffected > 0;
}
public bool Update(string tableName, KeyValuePair<string, object> matchKey, params KeyValuePair<string, object>[] values)
{
string setValues = string.Join(", ", values.Select(v => $"{v.Key} = @{v.Key}"));
string query = $"UPDATE {tableName} SET {setValues} WHERE {matchKey.Key} = @{matchKey.Key}";
var command = new SqlCommand(query, _connection);
foreach (var parameter in values)
{
var p = CreateParameter(parameter);
if (p is not null)
{
command.Parameters.Add(p);
}
}
command.Parameters.Add(CreateParameter(matchKey));
int rowsAffected = command.ExecuteNonQuery();
return rowsAffected > 0;
}
public async Task<bool> DeleteAsync(string tableName, params KeyValuePair<string, object>[] properties)
{
string whereClause = string.Join(" AND ", properties.Select(p => $"{p.Key} = @{p.Key}"));
string query = $"DELETE FROM {tableName} WHERE {whereClause}";
var command = new SqlCommand(query, _connection);
foreach (var parameter in properties)
{
var p = CreateParameter(parameter);
if (p is not null)
{
command.Parameters.Add(p);
}
}
return await command.ExecuteNonQueryAsync() > 0;
}
public bool Delete(string tableName, params KeyValuePair<string, object>[] properties)
{
string whereClause = string.Join(" AND ", properties.Select(p => $"{p.Key} = @{p.Key}"));
string query = $"DELETE FROM {tableName} WHERE {whereClause}";
var command = new SqlCommand(query, _connection);
foreach (var parameter in properties)
{
var p = CreateParameter(parameter);
if (p is not null)
{
command.Parameters.Add(p);
}
}
return command.ExecuteNonQuery() > 0;
}
public async Task<bool> ContainsAnyAsync(string tableName, params KeyValuePair<string, object>[] properties)
{
string whereClause = string.Join(" AND ", properties.Select(p => $"{p.Key} = @{p.Key}"));
string query = $"SELECT COUNT(*) FROM {tableName} WHERE {whereClause}";
var command = new SqlCommand(query, _connection);
foreach (var parameter in properties)
{
var p = CreateParameter(parameter);
if (p is not null)
{
command.Parameters.Add(p);
}
}
object? count = await command.ExecuteScalarAsync();
if (count is null)
{
return false;
}
return (int)count > 0;
}
public bool ContainsAny(string tableName, params KeyValuePair<string, object>[] properties)
{
string whereClause = string.Join(" AND ", properties.Select(p => $"{p.Key} = @{p.Key}"));
string query = $"SELECT COUNT(*) FROM {tableName} WHERE {whereClause}";
var command = new SqlCommand(query, _connection);
foreach (var parameter in properties)
{
var p = CreateParameter(parameter);
if (p is not null)
{
command.Parameters.Add(p);
}
}
object? count = command.ExecuteScalar();
if (count is null)
{
return false;
}
return (int)count > 0;
}
/// <summary>
/// Stop the connection to the SQL Database
/// </summary>
/// <returns></returns>
public async void Stop()
{
await _connection.CloseAsync();
}
/// <summary>
/// Execute a custom query
/// </summary>
/// <param name="query">The query</param>
/// <returns>The number of rows that the query modified</returns>
public async Task<int> ExecuteAsync(string query)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
await _connection.OpenAsync();
var command = new SqlCommand(query, _connection);
var answer = await command.ExecuteNonQueryAsync();
return answer;
}
/// <summary>
/// Execute a custom query
/// </summary>
/// <param name="query">The query</param>
/// <returns>The number of rows that the query modified</returns>
public int Execute(string query)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
_connection.Open();
var command = new SqlCommand(query, _connection);
var r = command.ExecuteNonQuery();
return r;
}
/// <summary>
/// Read data from the result table and return the first row
/// </summary>
/// <param name="query">The query</param>
/// <returns>The result is a string that has all values separated by space character</returns>
public async Task<string?> ReadDataAsync(string query)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
await _connection.OpenAsync();
var command = new SqlCommand(query, _connection);
var reader = await command.ExecuteReaderAsync();
var values = new object[reader.FieldCount];
if (reader.Read())
{
reader.GetValues(values);
return string.Join<object>(" ", values);
}
return null;
}
/// <summary>
/// Read data from the result table and return the first row
/// </summary>
/// <param name="query">The query</param>
/// <param name="parameters">The parameters of the query</param>
/// <returns>The result is a string that has all values separated by space character</returns>
public async Task<string?> ReadDataAsync(string query, params KeyValuePair<string, object>[] parameters)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
await _connection.OpenAsync();
var command = new SqlCommand(query, _connection);
foreach (var parameter in parameters)
{
var p = CreateParameter(parameter);
if (p is not null)
command.Parameters.Add(p);
}
var reader = await command.ExecuteReaderAsync();
var values = new object[reader.FieldCount];
if (reader.Read())
{
reader.GetValues(values);
return string.Join<object>(" ", values);
}
return null;
}
/// <summary>
/// Read data from the result table and return the first row
/// </summary>
/// <param name="query">The query</param>
/// <returns>The result is a string that has all values separated by space character</returns>
public string? ReadData(string query)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
_connection.Open();
var command = new SqlCommand(query, _connection);
var reader = command.ExecuteReader();
var values = new object[reader.FieldCount];
if (reader.Read())
{
reader.GetValues(values);
return string.Join<object>(" ", values);
}
return null;
}
/// <summary>
/// Read data from the result table and return the first row
/// </summary>
/// <param name="query">The query</param>
/// <returns>The first row as separated items</returns>
public async Task<object[]?> ReadDataArrayAsync(string query)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
await _connection.OpenAsync();
var command = new SqlCommand(query, _connection);
var reader = await command.ExecuteReaderAsync();
var values = new object[reader.FieldCount];
if (reader.Read())
{
reader.GetValues(values);
return values;
}
return null;
}
public async Task<object[]?> ReadDataArrayAsync(string query, params KeyValuePair<string, object>[] parameters)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
await _connection.OpenAsync();
var command = new SqlCommand(query, _connection);
foreach (var parameter in parameters)
{
var p = CreateParameter(parameter);
if (p is not null)
command.Parameters.Add(p);
}
var reader = await command.ExecuteReaderAsync();
var values = new object[reader.FieldCount];
if (reader.Read())
{
reader.GetValues(values);
return values;
}
return null;
}
/// <summary>
/// Read data from the result table and return the first row
/// </summary>
/// <param name="query">The query</param>
/// <returns>The first row as separated items</returns>
public object[]? ReadDataArray(string query)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
_connection.Open();
var command = new SqlCommand(query, _connection);
var reader = command.ExecuteReader();
var values = new object[reader.FieldCount];
if (reader.Read())
{
reader.GetValues(values);
return values;
}
return null;
}
/// <summary>
/// Read all rows from the result table and return them as a list of string arrays. The string arrays contain the
/// values of each row
/// </summary>
/// <param name="query">The query</param>
/// <returns>A list of string arrays representing the values that the query returns</returns>
public async Task<List<string[]>?> ReadAllRowsAsync(string query)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
await _connection.OpenAsync();
var command = new SqlCommand(query, _connection);
var reader = await command.ExecuteReaderAsync();
if (!reader.HasRows)
return null;
List<string[]> rows = new();
while (await reader.ReadAsync())
{
var values = new string[reader.FieldCount];
reader.GetValues(values);
rows.Add(values);
}
if (rows.Count == 0) return null;
return rows;
}
/// <summary>
/// Create a parameter for a query
/// </summary>
/// <param name="name">The name of the parameter</param>
/// <param name="value">The value of the parameter</param>
/// <returns>The SQLiteParameter that has the name, value and DBType set according to your inputs</returns>
private static SqlParameter? CreateParameter(string name, object value)
{
var parameter = new SqlParameter();
parameter.ParameterName = name;
parameter.Value = value;
if (value is string)
parameter.DbType = DbType.String;
else if (value is int)
parameter.DbType = DbType.Int32;
else if (value is long)
parameter.DbType = DbType.Int64;
else if (value is float)
parameter.DbType = DbType.Single;
else if (value is double)
parameter.DbType = DbType.Double;
else if (value is bool)
parameter.DbType = DbType.Boolean;
else if (value is DateTime)
parameter.DbType = DbType.DateTime;
else if (value is byte[])
parameter.DbType = DbType.Binary;
else if (value is Guid)
parameter.DbType = DbType.Guid;
else if (value is decimal)
parameter.DbType = DbType.Decimal;
else if (value is TimeSpan)
parameter.DbType = DbType.Time;
else if (value is DateTimeOffset)
parameter.DbType = DbType.DateTimeOffset;
else if (value is ushort)
parameter.DbType = DbType.UInt16;
else if (value is uint)
parameter.DbType = DbType.UInt32;
else if (value is ulong)
parameter.DbType = DbType.UInt64;
else if (value is sbyte)
parameter.DbType = DbType.SByte;
else if (value is short)
parameter.DbType = DbType.Int16;
else if (value is byte)
parameter.DbType = DbType.Byte;
else if (value is char)
parameter.DbType = DbType.StringFixedLength;
else if (value is char[])
parameter.DbType = DbType.StringFixedLength;
else
return null;
return parameter;
}
/// <summary>
/// Create a parameter for a query. The function automatically detects the type of the value.
/// </summary>
/// <param name="parameterValues">The parameter raw inputs. The Key is name and the Value is the value of the parameter</param>
/// <returns>The SQLiteParameter that has the name, value and DBType set according to your inputs</returns>
private static SqlParameter? CreateParameter(KeyValuePair<string, object> parameterValues)
{
return CreateParameter(parameterValues.Key, parameterValues.Value);
}
/// <summary>
/// Execute a query with parameters
/// </summary>
/// <param name="query">The query to execute</param>
/// <param name="parameters">The parameters of the query</param>
/// <returns>The number of rows that the query modified in the database</returns>
public async Task<int> ExecuteNonQueryAsync(string query, params KeyValuePair<string, object>[] parameters)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
await _connection.OpenAsync();
var command = new SqlCommand(query, _connection);
foreach (var parameter in parameters)
{
var p = CreateParameter(parameter);
if (p is not null)
command.Parameters.Add(p);
}
return await command.ExecuteNonQueryAsync();
}
/// <summary>
/// Execute a query with parameters that returns a specific type of object. The function will return the first row of the result transformed into the specified type.
/// </summary>
/// <param name="query">The query to execute</param>
/// <param name="convertor">The convertor function that will convert each row of the response into an object of <typeparamref name="T"/></param>
/// <param name="parameters">The parameters of the query</param>
/// <typeparam name="T">The return object type</typeparam>
/// <returns>An object of type T that represents the output of the convertor function based on the array of objects that the first row of the result has</returns>
public async Task<T?> ReadObjectOfTypeAsync<T>(string query, Func<object[], T> convertor, params KeyValuePair<string, object>[] parameters)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
await _connection.OpenAsync();
var command = new SqlCommand(query, _connection);
foreach (var parameter in parameters)
{
var p = CreateParameter(parameter);
if (p is not null)
command.Parameters.Add(p);
}
var reader = await command.ExecuteReaderAsync();
var values = new object[reader.FieldCount];
if (reader.Read())
{
reader.GetValues(values);
return convertor(values);
}
return default;
}
/// <summary>
/// Execute a query with parameters that returns a specific type of object. The function will return a list of objects of the specified type.
/// </summary>
/// <param name="query">The query to execute</param>
/// <param name="convertor">The convertor from object[] to T</param>
/// <param name="parameters">The parameters of the query</param>
/// <typeparam name="T">The expected object type</typeparam>
/// <returns>A list of objects of type T that represents each line of the output of the specified query, converted to T</returns>
public async Task<List<T>> ReadListOfTypeAsync<T>(string query, Func<object[], T> convertor,
params KeyValuePair<string, object>[] parameters)
{
if (!_connection.State.HasFlag(ConnectionState.Open))
await _connection.OpenAsync();
var command = new SqlCommand(query, _connection);
foreach (var parameter in parameters)
{
var p = CreateParameter(parameter);
if (p is not null)
command.Parameters.Add(p);
}
var reader = await command.ExecuteReaderAsync();
if (!reader.HasRows)
{
return new();
}
List<T> rows = new();
while (await reader.ReadAsync())
{
var values = new object[reader.FieldCount];
reader.GetValues(values);
rows.Add(convertor(values));
}
return rows;
}
}

View File

@@ -0,0 +1,17 @@
namespace RepoProvider.Models;
public class Dependency
{
public int Id { get; set; }
public string Name { get; set; }
public string DownloadLink { get; set; }
public bool IsExecutable { get; set; }
public Dependency(int id, string name, string downloadLink, bool isExecutable)
{
Id = id;
Name = name;
DownloadLink = downloadLink;
IsExecutable = isExecutable;
}
}

View File

@@ -0,0 +1,26 @@
namespace RepoProvider.Models;
public class Plugin
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Author { get; set; }
public string Version { get; set; }
public string DownloadLink { get; set; }
public int OperatingSystem { get; set; }
public bool IsApproved { get; set; }
public Plugin(int id, string name, string description, string author, string version, string downloadLink, int operatingSystem)
{
Id = id;
Name = name;
Description = description;
Author = author;
Version = version;
DownloadLink = downloadLink;
OperatingSystem = operatingSystem;
IsApproved = false;
}
}

View File

@@ -0,0 +1,95 @@
using RepoProvider.Database;
using RepoProvider.Models;
namespace RepoProvider.Providers;
public class DependenciesProvider : IDependenciesProvider
{
private readonly IDbManager _dbManager;
public DependenciesProvider(IDbManager dbManager)
{
_dbManager = dbManager;
}
public Task<List<Dependency>> GetAllDependencies()
{
string query = "SELECT * FROM Dependencies";
return _dbManager.ReadListOfTypeAsync(query, ConvertToDependency);
}
public async Task<List<Dependency>> GetDependenciesAsync(int pluginId)
{
string query = "SELECT DependencyId FROM PluginDependenciesLink WHERE PluginId = @PluginId";
List<int> dependenciesIds = await _dbManager.ReadListOfTypeAsync(query,
(arr) => (int)arr[0],
new KeyValuePair<string, object>("PluginId", pluginId));
List<Dependency> dependencies = new List<Dependency>();
foreach (int dependencyId in dependenciesIds)
{
Dependency? dependency = await GetDependencyAsync(dependencyId);
if (dependency != null)
{
dependencies.Add(dependency);
}
}
return dependencies;
}
public async Task<Dependency?> GetDependencyAsync(int dependencyId)
{
string query = "SELECT * FROM Dependencies WHERE Id = @DependencyId";
Dependency? dependency = await _dbManager.ReadObjectOfTypeAsync(query, ConvertToDependency, new KeyValuePair<string, object>("DependencyId", dependencyId));
return dependency;
}
public async Task<int> InsertDependency(Dependency dependency)
{
int? newId = await _dbManager.InsertAsyncWithReturn<int>("Dependencies", "Id",
new KeyValuePair<string, object>("Name", dependency.Name),
new KeyValuePair<string, object>("DownloadLink", dependency.DownloadLink),
new KeyValuePair<string, object>("IsExecutable", dependency.IsExecutable)
);
return newId.GetValueOrDefault(-1);
}
public async Task<bool> UpdateDependency(int dependencyId, Dependency dependency)
{
return await _dbManager.UpdateAsync("Dependencies", new KeyValuePair<string, object>("Id", dependencyId),
new KeyValuePair<string, object>("Name", dependency.Name),
new KeyValuePair<string, object>("DownloadLink", dependency.DownloadLink),
new KeyValuePair<string, object>("IsExecutable", dependency.IsExecutable)
);
}
public async Task<bool> LinkDependencyToPlugin(int dependencyId, int pluginId)
{
return await _dbManager.InsertAsync("PluginDependenciesLink",
new KeyValuePair<string, object>("PluginId", pluginId),
new KeyValuePair<string, object>("DependencyId", dependencyId)
);
}
public async Task<bool> EditDependency(int dependencyId, Dependency newDependency)
{
return await _dbManager.UpdateAsync("Dependencies", new KeyValuePair<string, object>("Id", dependencyId),
new KeyValuePair<string, object>("Name", newDependency.Name),
new KeyValuePair<string, object>("DownloadLink", newDependency.DownloadLink),
new KeyValuePair<string, object>("IsExecutable", newDependency.IsExecutable)
);
}
private Dependency ConvertToDependency(object[] arr)
{
int dependencyId = (int)arr[0];
string dependencyName = (string)arr[1];
string downloadLink = (string)arr[2];
bool isExecutable = (bool)arr[3];
return new Dependency(dependencyId, dependencyName, downloadLink, isExecutable);
}
}

View File

@@ -0,0 +1,57 @@
using RepoProvider.Models;
namespace RepoProvider.Providers;
public interface IDependenciesProvider
{
/// <summary>
/// Get all dependencies from the repository
/// </summary>
/// <returns>A list with all the dependencies from the repository</returns>
public Task<List<Dependency>> GetAllDependencies();
/// <summary>
/// Get all dependencies of a plugin
/// </summary>
/// <param name="pluginId">The id of the plugin</param>
/// <returns>A list of dependencies for the plugin</returns>
public Task<List<Dependency>> GetDependenciesAsync(int pluginId);
/// <summary>
/// Get the dependency by its id
/// </summary>
/// <param name="dependencyId">The dependency id</param>
/// <returns>All information about the dependency with the given id</returns>
public Task<Dependency?> GetDependencyAsync(int dependencyId);
/// <summary>
/// Inserts a dependency into the database
/// </summary>
/// <param name="dependency">The dependency to be inserted</param>
/// <returns>The id of the new dependency after the insertion; -1 if the dependency was not inserted successfully</returns>
public Task<int> InsertDependency(Dependency dependency);
/// <summary>
/// Updates the dependency with the given id
/// </summary>
/// <param name="dependencyId">The dependency to be updated</param>
/// <param name="dependency">The new dependency to be given to the specified id</param>
/// <returns>true if the operation succeeded; false otherwise</returns>
public Task<bool> UpdateDependency(int dependencyId, Dependency dependency);
/// <summary>
/// Links a dependency to a plugin
/// </summary>
/// <param name="dependencyId">The dependency id</param>
/// <param name="pluginId">The plugin id</param>
/// <returns>true if the link succeeded; false otherwise</returns>
public Task<bool> LinkDependencyToPlugin(int dependencyId, int pluginId);
/// <summary>
/// Edit a dependency
/// </summary>
/// <param name="dependencyId">The dependency id of the dependency that will be edited</param>
/// <param name="newDependency">The new dependency that will replace the old one</param>
/// <returns></returns>
public Task<bool> EditDependency(int dependencyId, Dependency newDependency);
}

View File

@@ -0,0 +1,69 @@
using RepoProvider.Models;
namespace RepoProvider.Providers;
public interface IPluginProvider
{
/// <summary>
/// Get a plugin by its name
/// </summary>
/// <param name="pluginName">The name of the plugin</param>
/// <param name="operatingSystem">The operating system under this plugin runs</param>
/// <param name="includeNotApproved">Check even on the not approved plugins list</param>
/// <returns>The plugin that has the specified name; null otherwise</returns>
public Task<Plugin?> GetPluginByName(string pluginName, int operatingSystem, bool includeNotApproved);
/// <summary>
/// Get a plugin by its id
/// </summary>
/// <param name="pluginId">The id of the plugin</param>
/// <param name="includeNotApproved">Check even in the not approved plugins list</param>
/// <returns>The plugin that has the specified id; null otherwise</returns>
public Task<Plugin?> GetPluginById(int pluginId, bool includeNotApproved);
/// <summary>
/// Get all plugins from the repository based on the operating system
/// </summary>
/// <param name="operatingSystem">The operating system specified</param>
/// <param name="includeNotApproved">Include non-approved plugins in the list</param>
/// <returns>All plugins that can run on the specified operating system</returns>
public Task<List<Plugin>> GetAllPlugins(int operatingSystem, bool includeNotApproved);
/// <summary>
/// Get all plugins from the repository
/// </summary>
/// <param name="includeNotApproved">Include non approved plugins in the list</param>
/// <returns>All plugins from the repository</returns>
public Task<List<Plugin>> GetAllPlugins(bool includeNotApproved);
/// <summary>
/// Inserts a plugin into the database
/// </summary>
/// <param name="plugin">The plugin to be inserted</param>
/// <returns>The id of the plugin after the insertion; -1 if the plugin could not be inserted successfully</returns>
public Task<int> InsertPlugin(Plugin plugin);
/// <summary>
/// Update an existing plugin
/// </summary>
/// <param name="pluginId">The plugin id</param>
/// <param name="newPluginValues">The new values for the plugin</param>
/// <returns>true if the update succeeded; false otherwise </returns>
public Task<bool> UpdatePlugin(int pluginId, Plugin newPluginValues);
/// <summary>
/// Delete an existing plugin
/// </summary>
/// <param name="pluginId">The id of the plugin to be deleted</param>
/// <returns>true if the deletion succeeded; false otherwise</returns>
public Task<bool> DeletePlugin(int pluginId);
/// <summary>
/// Approve a plugin
/// </summary>
/// <param name="pluginId">The id of the plugin that will be approved</param>
/// <returns>true if the operation succeeds; false otherwise</returns>
public Task<bool> ApprovePlugin(int pluginId);
}

View File

@@ -0,0 +1,136 @@
using RepoProvider.Database;
using RepoProvider.Models;
namespace RepoProvider.Providers;
public class PluginsProvider : IPluginProvider
{
private readonly IDbManager _dbManager;
public PluginsProvider(IDbManager dbManager)
{
_dbManager = dbManager;
}
public async Task<Plugin?> GetPluginByName(string pluginName, int operatingSystem, bool includeNotApproved)
{
if (includeNotApproved)
{
string query = "SELECT * FROM Plugins WHERE Name = @PluginName AND OperatingSystem = @OperatingSystem";
Plugin? plugin = await _dbManager.ReadObjectOfTypeAsync(query, ConvertToPlugin,
new KeyValuePair<string, object>("PluginName", pluginName),
new KeyValuePair<string, object>("OperatingSystem", operatingSystem));
return plugin;
}
else
{
string query = "SELECT * FROM Plugins WHERE Name = @PluginName AND OperatingSystem = @OperatingSystem AND IsApproved = true";
Plugin? plugin = await _dbManager.ReadObjectOfTypeAsync(query, ConvertToPlugin,
new KeyValuePair<string, object>("PluginName", pluginName),
new KeyValuePair<string, object>("OperatingSystem", operatingSystem));
return plugin;
}
}
public async Task<Plugin?> GetPluginById(int pluginId, bool includeNotApproved)
{
if (includeNotApproved)
{
string query = "SELECT * FROM Plugins WHERE Id = @PluginId";
Plugin? plugin = await _dbManager.ReadObjectOfTypeAsync(query, ConvertToPlugin, new KeyValuePair<string, object>("PluginId", pluginId));
return plugin;
}
else
{
string query = "SELECT * FROM Plugins WHERE Id = @PluginId and IsApproved = true";
Plugin? plugin = await _dbManager.ReadObjectOfTypeAsync(query, ConvertToPlugin, new KeyValuePair<string, object>("PluginId", pluginId));
return plugin;
}
}
public async Task<List<Plugin>> GetAllPlugins(int operatingSystem, bool includeNotApproved)
{
if (includeNotApproved)
{
string query = "SELECT * FROM Plugins WHERE OperatingSystem = @OperatingSystem";
List<Plugin> plugins = await _dbManager.ReadListOfTypeAsync(query, ConvertToPlugin, new KeyValuePair<string, object>("OperatingSystem", operatingSystem));
return plugins;
}
else
{
string query = "SELECT * FROM Plugins WHERE OperatingSystem = @OperatingSystem and IsApproved = true";
List<Plugin> plugins = await _dbManager.ReadListOfTypeAsync(query, ConvertToPlugin, new KeyValuePair<string, object>("OperatingSystem", operatingSystem));
return plugins;
}
}
public async Task<List<Plugin>> GetAllPlugins(bool includeNotApproved)
{
if (includeNotApproved)
{
string query = "SELECT * FROM Plugins";
List<Plugin> plugins = await _dbManager.ReadListOfTypeAsync(query, ConvertToPlugin);
return plugins;
}
else
{
string query = "SELECT * FROM Plugins WHERE IsApproved = true";
List<Plugin> plugins = await _dbManager.ReadListOfTypeAsync(query, ConvertToPlugin);
return plugins;
}
}
public async Task<int> InsertPlugin(Plugin plugin)
{
int? newId = await _dbManager.InsertAsyncWithReturn<int>("Plugins", "Id",
new KeyValuePair<string, object>("Name", plugin.Name),
new KeyValuePair<string, object>("Description", plugin.Description),
new KeyValuePair<string, object>("Author", plugin.Author),
new KeyValuePair<string, object>("Version", plugin.Version),
new KeyValuePair<string, object>("DownloadLink", plugin.DownloadLink),
new KeyValuePair<string, object>("OperatingSystem", plugin.OperatingSystem),
new KeyValuePair<string, object>("IsApproved", plugin.IsApproved)
);
return newId.GetValueOrDefault(-1);
}
public async Task<bool> UpdatePlugin(int pluginId, Plugin newPluginValues)
{
return await _dbManager.UpdateAsync("Plugins", new KeyValuePair<string, object>("Id", pluginId),
new KeyValuePair<string, object>("Name", newPluginValues.Name),
new KeyValuePair<string, object>("Description", newPluginValues.Description),
new KeyValuePair<string, object>("Author", newPluginValues.Author),
new KeyValuePair<string, object>("Version", newPluginValues.Version),
new KeyValuePair<string, object>("DownloadLink", newPluginValues.DownloadLink),
new KeyValuePair<string, object>("OperatingSystem", newPluginValues.OperatingSystem),
new KeyValuePair<string, object>("IsApproved", newPluginValues.IsApproved)
);
}
public async Task<bool> DeletePlugin(int pluginId)
{
return await _dbManager.DeleteAsync("Plugins", new KeyValuePair<string, object>("Id", pluginId));
}
public async Task<bool> ApprovePlugin(int pluginId)
{
return await _dbManager.UpdateAsync("Plugins",
new KeyValuePair<string, object>("Id", pluginId),
new KeyValuePair<string, object>("IsApproved", true));
}
private Plugin ConvertToPlugin(object[] arr)
{
int pluginId = (int)arr[0];
string pluginName = (string)arr[1];
string pluginDescription = (string)arr[2];
string pluginAuthor = (string)arr[3];
string pluginVersion = (string)arr[4];
string pluginLink = (string)arr[5];
int operatingSystem = (int)arr[6];
bool isApproved = (bool)arr[7];
return new Plugin(pluginId, pluginName, pluginDescription, pluginAuthor, pluginVersion, pluginLink, operatingSystem) {IsApproved = isApproved};
}
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.1" />
<PackageReference Include="Npgsql" Version="9.0.3" />
</ItemGroup>
</Project>

27
SethRepository.sln Normal file
View File

@@ -0,0 +1,27 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SethRepository", "SethRepository\SethRepository.csproj", "{7E5A2E3A-9D2C-44B1-8305-E79256661D1C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B1D57101-5D61-4BDB-9468-4D89BA4C277C}"
ProjectSection(SolutionItems) = preProject
compose.yaml = compose.yaml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RepoProvider", "RepoProvider\RepoProvider.csproj", "{1DB2CDF5-9062-48FA-801F-CD8E0066A27A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7E5A2E3A-9D2C-44B1-8305-E79256661D1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7E5A2E3A-9D2C-44B1-8305-E79256661D1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E5A2E3A-9D2C-44B1-8305-E79256661D1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E5A2E3A-9D2C-44B1-8305-E79256661D1C}.Release|Any CPU.Build.0 = Release|Any CPU
{1DB2CDF5-9062-48FA-801F-CD8E0066A27A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1DB2CDF5-9062-48FA-801F-CD8E0066A27A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1DB2CDF5-9062-48FA-801F-CD8E0066A27A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1DB2CDF5-9062-48FA-801F-CD8E0066A27A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,4 @@
<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/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APhysicalFileProvider_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F661861d2f496c2dbfec4ea5554546fc42e67528c7f93c28d0c05b814a7991_003FPhysicalFileProvider_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASqlCommand_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc78de2a4f91942a6880cddca71a392ad14288_003F75_003F5a311123_003FSqlCommand_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
</wpf:ResourceDictionary>

View File

@@ -0,0 +1,40 @@
using Microsoft.AspNetCore.Mvc;
using RepoProvider.Providers;
using SethRepository.Utilities;
namespace SethRepository.Controllers;
[ApiController]
[Route("api/v1/dependency")]
public class DependencyRepoV1Controller : Controller
{
private readonly IDependenciesProvider _dependenciesProvider;
public DependencyRepoV1Controller(IDependenciesProvider dependenciesProvider)
{
_dependenciesProvider = dependenciesProvider;
}
[HttpGet]
[Route("get-by-plugin-id")]
public async Task<IActionResult> GetDependenciesByPluginId([FromQuery] int pluginId)
{
var dependencies = await _dependenciesProvider.GetDependenciesAsync(pluginId);
var jsonString = await JsonConvertor.ConvertToJsonString(dependencies);
return Ok(jsonString);
}
[HttpGet]
[Route("get-by-id")]
public async Task<IActionResult> GetDependencyById([FromQuery] int dependencyId)
{
var dependency = await _dependenciesProvider.GetDependencyAsync(dependencyId);
if (dependency is null)
{
return NotFound("Dependency not found");
}
var jsonString = await JsonConvertor.ConvertToJsonString(dependency);
return Ok(jsonString);
}
}

View File

@@ -0,0 +1,53 @@
using Microsoft.AspNetCore.Mvc;
using RepoProvider.Providers;
using SethRepository.Utilities;
namespace SethRepository.Controllers;
[ApiController]
[Route("/api/v1/plugin")]
public class PluginRepoV1Controller : Controller
{
private readonly IPluginProvider _pluginProvider;
public PluginRepoV1Controller(IPluginProvider pluginProvider)
{
_pluginProvider = pluginProvider;
}
[HttpGet]
[Route("get-by-name")]
public async Task<IActionResult> GetPluginByName([FromQuery] string pluginName, [FromQuery] int operatingSystem, [FromQuery] bool includeNotApproved)
{
var plugin = await _pluginProvider.GetPluginByName(pluginName, operatingSystem, includeNotApproved);
if (plugin is null)
{
return NotFound();
}
var jsonString = await JsonConvertor.ConvertToJsonString(plugin);
return Ok(jsonString);
}
[HttpGet]
[Route("get-by-id")]
public async Task<IActionResult> GetPluginById([FromQuery] int pluginId, [FromQuery] bool includeNotApproved)
{
var plugin = await _pluginProvider.GetPluginById(pluginId, includeNotApproved);
if (plugin is null)
{
return NotFound();
}
var jsonString = await JsonConvertor.ConvertToJsonString(plugin);
return Ok(jsonString);
}
[HttpGet]
[Route("get-all-plugins")]
public async Task<IActionResult> GetAllPlugins([FromQuery] int operatingSystem, [FromQuery] bool includeNotApproved)
{
var plugins = await _pluginProvider.GetAllPlugins(operatingSystem, includeNotApproved);
var jsonString = await JsonConvertor.ConvertToJsonString(plugins);
return Ok(jsonString);
}
}

23
SethRepository/Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["SethRepository/SethRepository.csproj", "SethRepository/"]
RUN dotnet restore "SethRepository/SethRepository.csproj"
COPY . .
WORKDIR "/src/SethRepository"
RUN dotnet build "SethRepository.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "SethRepository.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "SethRepository.dll"]

57
SethRepository/Program.cs Normal file
View File

@@ -0,0 +1,57 @@
using RepoProvider.Database;
using RepoProvider.Providers;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IDatabaseConnectionDetails>(sp =>
new DatabaseConnectionDetails("172.21.100.2", "SethRepository", "postgres", "Password123"));
builder.Services.AddSingleton<IDbManager>(sp =>
{
IDatabaseConnectionDetails? connectionDetails = sp.GetService<IDatabaseConnectionDetails>();
if (connectionDetails is null)
{
throw new InvalidOperationException("Database connection details not found");
}
return new PostgresDbManager(connectionDetails);
});
builder.Services.AddSingleton<IDependenciesProvider>(sp =>
{
IDbManager? dbManager = sp.GetService<IDbManager>();
if (dbManager is null)
{
throw new InvalidOperationException("Database manager not found");
}
return new DependenciesProvider(dbManager);
});
builder.Services.AddSingleton<IPluginProvider>(sp =>
{
IDbManager? dbManager = sp.GetService<IDbManager>();
if (dbManager is null)
{
throw new InvalidOperationException("Database manager not found");
}
return new PluginsProvider(dbManager);
});
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseRouting();
app.MapControllers();
app.UseAuthorization();
app.UseSwagger();
app.UseSwaggerUI();
app.Run();

View File

@@ -0,0 +1,38 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:8180",
"sslPort": 44309
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5022",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:7265;http://localhost:5022",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": false,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,87 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<Content Include="..\.dockerignore">
<Link>.dockerignore</Link>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RepoProvider\RepoProvider.csproj" />
</ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="wwwroot\css\site.css" />
<_ContentIncludedByDefault Remove="wwwroot\favicon.ico" />
<_ContentIncludedByDefault Remove="wwwroot\js\site.js" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.css" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.min.css" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.min.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.rtl.css" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.rtl.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.rtl.min.css" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-grid.rtl.min.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.css" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.min.css" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.min.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.rtl.css" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.rtl.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.rtl.min.css" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-reboot.rtl.min.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-utilities.css" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-utilities.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-utilities.min.css" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-utilities.min.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-utilities.rtl.css" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-utilities.rtl.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-utilities.rtl.min.css" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap-utilities.rtl.min.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.css" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.min.css" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.min.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.rtl.css" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.rtl.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.rtl.min.css" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\css\bootstrap.rtl.min.css.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.bundle.js" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.bundle.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.bundle.min.js" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.bundle.min.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.esm.js" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.esm.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.esm.min.js" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.esm.min.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.js" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.min.js" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\dist\js\bootstrap.min.js.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\bootstrap\LICENSE" />
<_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation-unobtrusive\jquery.validate.unobtrusive.js" />
<_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation-unobtrusive\jquery.validate.unobtrusive.min.js" />
<_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation-unobtrusive\LICENSE.txt" />
<_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation\dist\additional-methods.js" />
<_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation\dist\additional-methods.min.js" />
<_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation\dist\jquery.validate.js" />
<_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation\dist\jquery.validate.min.js" />
<_ContentIncludedByDefault Remove="wwwroot\lib\jquery-validation\LICENSE.md" />
<_ContentIncludedByDefault Remove="wwwroot\lib\jquery\dist\jquery.js" />
<_ContentIncludedByDefault Remove="wwwroot\lib\jquery\dist\jquery.min.js" />
<_ContentIncludedByDefault Remove="wwwroot\lib\jquery\dist\jquery.min.map" />
<_ContentIncludedByDefault Remove="wwwroot\lib\jquery\LICENSE.txt" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.0" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ActiveDebugProfile>https</ActiveDebugProfile>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,20 @@
using System.Text;
using System.Text.Json;
namespace SethRepository.Utilities;
internal static class JsonConvertor
{
internal static async Task<string> ConvertToJsonString<T>(T data)
{
var memoryStream = new MemoryStream();
await JsonSerializer.SerializeAsync(memoryStream, data, typeof(T), new JsonSerializerOptions
{
WriteIndented = false,
});
var result = Encoding.ASCII.GetString(memoryStream.ToArray());
await memoryStream.FlushAsync();
memoryStream.Close();
return result;
}
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,22 @@
html {
font-size: 14px;
}
@media (min-width: 768px) {
html {
font-size: 16px;
}
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
html {
position: relative;
min-height: 100%;
}
body {
margin-bottom: 60px;
}

View File

View File

@@ -0,0 +1,4 @@
// Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
// for details on configuring this project to bundle and minify static web assets.
// Write your JavaScript code.

View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2011-2021 Twitter, Inc.
Copyright (c) 2011-2021 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,427 @@
/*!
* Bootstrap Reboot v5.1.0 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
background-color: currentColor;
border: 0;
opacity: 0.25;
}
hr:not([size]) {
height: 1px;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-bs-original-title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-left: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-left: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.2em;
background-color: #fcf8e3;
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: #0d6efd;
text-decoration: underline;
}
a:hover {
color: #0a58ca;
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
direction: ltr /* rtl:ignore */;
unicode-bidi: bidi-override;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: #d63384;
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.2rem 0.4rem;
font-size: 0.875em;
color: #fff;
background-color: #212529;
border-radius: 0.2rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
font-weight: 700;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: #6c757d;
text-align: left;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]::-webkit-calendar-picker-indicator {
display: none;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: left;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: left;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
outline-offset: -2px;
-webkit-appearance: textfield;
}
/* rtl:raw:
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::file-selector-button {
font: inherit;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.css.map */

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
/*!
* Bootstrap Reboot v5.1.0 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.min.css.map */

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,424 @@
/*!
* Bootstrap Reboot v5.1.0 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/
*,
*::before,
*::after {
box-sizing: border-box;
}
@media (prefers-reduced-motion: no-preference) {
:root {
scroll-behavior: smooth;
}
}
body {
margin: 0;
font-family: var(--bs-body-font-family);
font-size: var(--bs-body-font-size);
font-weight: var(--bs-body-font-weight);
line-height: var(--bs-body-line-height);
color: var(--bs-body-color);
text-align: var(--bs-body-text-align);
background-color: var(--bs-body-bg);
-webkit-text-size-adjust: 100%;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
hr {
margin: 1rem 0;
color: inherit;
background-color: currentColor;
border: 0;
opacity: 0.25;
}
hr:not([size]) {
height: 1px;
}
h6, h5, h4, h3, h2, h1 {
margin-top: 0;
margin-bottom: 0.5rem;
font-weight: 500;
line-height: 1.2;
}
h1 {
font-size: calc(1.375rem + 1.5vw);
}
@media (min-width: 1200px) {
h1 {
font-size: 2.5rem;
}
}
h2 {
font-size: calc(1.325rem + 0.9vw);
}
@media (min-width: 1200px) {
h2 {
font-size: 2rem;
}
}
h3 {
font-size: calc(1.3rem + 0.6vw);
}
@media (min-width: 1200px) {
h3 {
font-size: 1.75rem;
}
}
h4 {
font-size: calc(1.275rem + 0.3vw);
}
@media (min-width: 1200px) {
h4 {
font-size: 1.5rem;
}
}
h5 {
font-size: 1.25rem;
}
h6 {
font-size: 1rem;
}
p {
margin-top: 0;
margin-bottom: 1rem;
}
abbr[title],
abbr[data-bs-original-title] {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
cursor: help;
-webkit-text-decoration-skip-ink: none;
text-decoration-skip-ink: none;
}
address {
margin-bottom: 1rem;
font-style: normal;
line-height: inherit;
}
ol,
ul {
padding-right: 2rem;
}
ol,
ul,
dl {
margin-top: 0;
margin-bottom: 1rem;
}
ol ol,
ul ul,
ol ul,
ul ol {
margin-bottom: 0;
}
dt {
font-weight: 700;
}
dd {
margin-bottom: 0.5rem;
margin-right: 0;
}
blockquote {
margin: 0 0 1rem;
}
b,
strong {
font-weight: bolder;
}
small {
font-size: 0.875em;
}
mark {
padding: 0.2em;
background-color: #fcf8e3;
}
sub,
sup {
position: relative;
font-size: 0.75em;
line-height: 0;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
a {
color: #0d6efd;
text-decoration: underline;
}
a:hover {
color: #0a58ca;
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
color: inherit;
text-decoration: none;
}
pre,
code,
kbd,
samp {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 1em;
direction: ltr ;
unicode-bidi: bidi-override;
}
pre {
display: block;
margin-top: 0;
margin-bottom: 1rem;
overflow: auto;
font-size: 0.875em;
}
pre code {
font-size: inherit;
color: inherit;
word-break: normal;
}
code {
font-size: 0.875em;
color: #d63384;
word-wrap: break-word;
}
a > code {
color: inherit;
}
kbd {
padding: 0.2rem 0.4rem;
font-size: 0.875em;
color: #fff;
background-color: #212529;
border-radius: 0.2rem;
}
kbd kbd {
padding: 0;
font-size: 1em;
font-weight: 700;
}
figure {
margin: 0 0 1rem;
}
img,
svg {
vertical-align: middle;
}
table {
caption-side: bottom;
border-collapse: collapse;
}
caption {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
color: #6c757d;
text-align: right;
}
th {
text-align: inherit;
text-align: -webkit-match-parent;
}
thead,
tbody,
tfoot,
tr,
td,
th {
border-color: inherit;
border-style: solid;
border-width: 0;
}
label {
display: inline-block;
}
button {
border-radius: 0;
}
button:focus:not(:focus-visible) {
outline: 0;
}
input,
button,
select,
optgroup,
textarea {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
button,
select {
text-transform: none;
}
[role=button] {
cursor: pointer;
}
select {
word-wrap: normal;
}
select:disabled {
opacity: 1;
}
[list]::-webkit-calendar-picker-indicator {
display: none;
}
button,
[type=button],
[type=reset],
[type=submit] {
-webkit-appearance: button;
}
button:not(:disabled),
[type=button]:not(:disabled),
[type=reset]:not(:disabled),
[type=submit]:not(:disabled) {
cursor: pointer;
}
::-moz-focus-inner {
padding: 0;
border-style: none;
}
textarea {
resize: vertical;
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
float: right;
width: 100%;
padding: 0;
margin-bottom: 0.5rem;
font-size: calc(1.275rem + 0.3vw);
line-height: inherit;
}
@media (min-width: 1200px) {
legend {
font-size: 1.5rem;
}
}
legend + * {
clear: right;
}
::-webkit-datetime-edit-fields-wrapper,
::-webkit-datetime-edit-text,
::-webkit-datetime-edit-minute,
::-webkit-datetime-edit-hour-field,
::-webkit-datetime-edit-day-field,
::-webkit-datetime-edit-month-field,
::-webkit-datetime-edit-year-field {
padding: 0;
}
::-webkit-inner-spin-button {
height: auto;
}
[type=search] {
outline-offset: -2px;
-webkit-appearance: textfield;
}
[type="tel"],
[type="url"],
[type="email"],
[type="number"] {
direction: ltr;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-color-swatch-wrapper {
padding: 0;
}
::file-selector-button {
font: inherit;
}
::-webkit-file-upload-button {
font: inherit;
-webkit-appearance: button;
}
output {
display: inline-block;
}
iframe {
border: 0;
}
summary {
display: list-item;
cursor: pointer;
}
progress {
vertical-align: baseline;
}
[hidden] {
display: none !important;
}
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
/*!
* Bootstrap Reboot v5.1.0 (https://getbootstrap.com/)
* Copyright 2011-2021 The Bootstrap Authors
* Copyright 2011-2021 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
*/*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-right:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-right:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:right}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:right;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:right}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}[type=email],[type=number],[type=tel],[type=url]{direction:ltr}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}
/*# sourceMappingURL=bootstrap-reboot.rtl.min.css.map */

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (c) .NET Foundation and Contributors
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,435 @@
/**
* @license
* Unobtrusive validation support library for jQuery and jQuery Validate
* Copyright (c) .NET Foundation. All rights reserved.
* Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
* @version v4.0.0
*/
/*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false */
/*global document: false, jQuery: false */
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define("jquery.validate.unobtrusive", ['jquery-validation'], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS-like environments that support module.exports
module.exports = factory(require('jquery-validation'));
} else {
// Browser global
jQuery.validator.unobtrusive = factory(jQuery);
}
}(function ($) {
var $jQval = $.validator,
adapters,
data_validation = "unobtrusiveValidation";
function setValidationValues(options, ruleName, value) {
options.rules[ruleName] = value;
if (options.message) {
options.messages[ruleName] = options.message;
}
}
function splitAndTrim(value) {
return value.replace(/^\s+|\s+$/g, "").split(/\s*,\s*/g);
}
function escapeAttributeValue(value) {
// As mentioned on http://api.jquery.com/category/selectors/
return value.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1");
}
function getModelPrefix(fieldName) {
return fieldName.substr(0, fieldName.lastIndexOf(".") + 1);
}
function appendModelPrefix(value, prefix) {
if (value.indexOf("*.") === 0) {
value = value.replace("*.", prefix);
}
return value;
}
function onError(error, inputElement) { // 'this' is the form element
var container = $(this).find("[data-valmsg-for='" + escapeAttributeValue(inputElement[0].name) + "']"),
replaceAttrValue = container.attr("data-valmsg-replace"),
replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) !== false : null;
container.removeClass("field-validation-valid").addClass("field-validation-error");
error.data("unobtrusiveContainer", container);
if (replace) {
container.empty();
error.removeClass("input-validation-error").appendTo(container);
}
else {
error.hide();
}
}
function onErrors(event, validator) { // 'this' is the form element
var container = $(this).find("[data-valmsg-summary=true]"),
list = container.find("ul");
if (list && list.length && validator.errorList.length) {
list.empty();
container.addClass("validation-summary-errors").removeClass("validation-summary-valid");
$.each(validator.errorList, function () {
$("<li />").html(this.message).appendTo(list);
});
}
}
function onSuccess(error) { // 'this' is the form element
var container = error.data("unobtrusiveContainer");
if (container) {
var replaceAttrValue = container.attr("data-valmsg-replace"),
replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) : null;
container.addClass("field-validation-valid").removeClass("field-validation-error");
error.removeData("unobtrusiveContainer");
if (replace) {
container.empty();
}
}
}
function onReset(event) { // 'this' is the form element
var $form = $(this),
key = '__jquery_unobtrusive_validation_form_reset';
if ($form.data(key)) {
return;
}
// Set a flag that indicates we're currently resetting the form.
$form.data(key, true);
try {
$form.data("validator").resetForm();
} finally {
$form.removeData(key);
}
$form.find(".validation-summary-errors")
.addClass("validation-summary-valid")
.removeClass("validation-summary-errors");
$form.find(".field-validation-error")
.addClass("field-validation-valid")
.removeClass("field-validation-error")
.removeData("unobtrusiveContainer")
.find(">*") // If we were using valmsg-replace, get the underlying error
.removeData("unobtrusiveContainer");
}
function validationInfo(form) {
var $form = $(form),
result = $form.data(data_validation),
onResetProxy = $.proxy(onReset, form),
defaultOptions = $jQval.unobtrusive.options || {},
execInContext = function (name, args) {
var func = defaultOptions[name];
func && $.isFunction(func) && func.apply(form, args);
};
if (!result) {
result = {
options: { // options structure passed to jQuery Validate's validate() method
errorClass: defaultOptions.errorClass || "input-validation-error",
errorElement: defaultOptions.errorElement || "span",
errorPlacement: function () {
onError.apply(form, arguments);
execInContext("errorPlacement", arguments);
},
invalidHandler: function () {
onErrors.apply(form, arguments);
execInContext("invalidHandler", arguments);
},
messages: {},
rules: {},
success: function () {
onSuccess.apply(form, arguments);
execInContext("success", arguments);
}
},
attachValidation: function () {
$form
.off("reset." + data_validation, onResetProxy)
.on("reset." + data_validation, onResetProxy)
.validate(this.options);
},
validate: function () { // a validation function that is called by unobtrusive Ajax
$form.validate();
return $form.valid();
}
};
$form.data(data_validation, result);
}
return result;
}
$jQval.unobtrusive = {
adapters: [],
parseElement: function (element, skipAttach) {
/// <summary>
/// Parses a single HTML element for unobtrusive validation attributes.
/// </summary>
/// <param name="element" domElement="true">The HTML element to be parsed.</param>
/// <param name="skipAttach" type="Boolean">[Optional] true to skip attaching the
/// validation to the form. If parsing just this single element, you should specify true.
/// If parsing several elements, you should specify false, and manually attach the validation
/// to the form when you are finished. The default is false.</param>
var $element = $(element),
form = $element.parents("form")[0],
valInfo, rules, messages;
if (!form) { // Cannot do client-side validation without a form
return;
}
valInfo = validationInfo(form);
valInfo.options.rules[element.name] = rules = {};
valInfo.options.messages[element.name] = messages = {};
$.each(this.adapters, function () {
var prefix = "data-val-" + this.name,
message = $element.attr(prefix),
paramValues = {};
if (message !== undefined) { // Compare against undefined, because an empty message is legal (and falsy)
prefix += "-";
$.each(this.params, function () {
paramValues[this] = $element.attr(prefix + this);
});
this.adapt({
element: element,
form: form,
message: message,
params: paramValues,
rules: rules,
messages: messages
});
}
});
$.extend(rules, { "__dummy__": true });
if (!skipAttach) {
valInfo.attachValidation();
}
},
parse: function (selector) {
/// <summary>
/// Parses all the HTML elements in the specified selector. It looks for input elements decorated
/// with the [data-val=true] attribute value and enables validation according to the data-val-*
/// attribute values.
/// </summary>
/// <param name="selector" type="String">Any valid jQuery selector.</param>
// $forms includes all forms in selector's DOM hierarchy (parent, children and self) that have at least one
// element with data-val=true
var $selector = $(selector),
$forms = $selector.parents()
.addBack()
.filter("form")
.add($selector.find("form"))
.has("[data-val=true]");
$selector.find("[data-val=true]").each(function () {
$jQval.unobtrusive.parseElement(this, true);
});
$forms.each(function () {
var info = validationInfo(this);
if (info) {
info.attachValidation();
}
});
}
};
adapters = $jQval.unobtrusive.adapters;
adapters.add = function (adapterName, params, fn) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
/// <param name="params" type="Array" optional="true">[Optional] An array of parameter names (strings) that will
/// be extracted from the data-val-nnnn-mmmm HTML attributes (where nnnn is the adapter name, and
/// mmmm is the parameter name).</param>
/// <param name="fn" type="Function">The function to call, which adapts the values from the HTML
/// attributes into jQuery Validate rules and/or messages.</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
if (!fn) { // Called with no params, just a function
fn = params;
params = [];
}
this.push({ name: adapterName, params: params, adapt: fn });
return this;
};
adapters.addBool = function (adapterName, ruleName) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
/// the jQuery Validate validation rule has no parameter values.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
/// <param name="ruleName" type="String" optional="true">[Optional] The name of the jQuery Validate rule. If not provided, the value
/// of adapterName will be used instead.</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
return this.add(adapterName, function (options) {
setValidationValues(options, ruleName || adapterName, true);
});
};
adapters.addMinMax = function (adapterName, minRuleName, maxRuleName, minMaxRuleName, minAttribute, maxAttribute) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
/// the jQuery Validate validation has three potential rules (one for min-only, one for max-only, and
/// one for min-and-max). The HTML parameters are expected to be named -min and -max.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute (where nnnn is the adapter name).</param>
/// <param name="minRuleName" type="String">The name of the jQuery Validate rule to be used when you only
/// have a minimum value.</param>
/// <param name="maxRuleName" type="String">The name of the jQuery Validate rule to be used when you only
/// have a maximum value.</param>
/// <param name="minMaxRuleName" type="String">The name of the jQuery Validate rule to be used when you
/// have both a minimum and maximum value.</param>
/// <param name="minAttribute" type="String" optional="true">[Optional] The name of the HTML attribute that
/// contains the minimum value. The default is "min".</param>
/// <param name="maxAttribute" type="String" optional="true">[Optional] The name of the HTML attribute that
/// contains the maximum value. The default is "max".</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
return this.add(adapterName, [minAttribute || "min", maxAttribute || "max"], function (options) {
var min = options.params.min,
max = options.params.max;
if (min && max) {
setValidationValues(options, minMaxRuleName, [min, max]);
}
else if (min) {
setValidationValues(options, minRuleName, min);
}
else if (max) {
setValidationValues(options, maxRuleName, max);
}
});
};
adapters.addSingleVal = function (adapterName, attribute, ruleName) {
/// <summary>Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where
/// the jQuery Validate validation rule has a single value.</summary>
/// <param name="adapterName" type="String">The name of the adapter to be added. This matches the name used
/// in the data-val-nnnn HTML attribute(where nnnn is the adapter name).</param>
/// <param name="attribute" type="String">[Optional] The name of the HTML attribute that contains the value.
/// The default is "val".</param>
/// <param name="ruleName" type="String" optional="true">[Optional] The name of the jQuery Validate rule. If not provided, the value
/// of adapterName will be used instead.</param>
/// <returns type="jQuery.validator.unobtrusive.adapters" />
return this.add(adapterName, [attribute || "val"], function (options) {
setValidationValues(options, ruleName || adapterName, options.params[attribute]);
});
};
$jQval.addMethod("__dummy__", function (value, element, params) {
return true;
});
$jQval.addMethod("regex", function (value, element, params) {
var match;
if (this.optional(element)) {
return true;
}
match = new RegExp(params).exec(value);
return (match && (match.index === 0) && (match[0].length === value.length));
});
$jQval.addMethod("nonalphamin", function (value, element, nonalphamin) {
var match;
if (nonalphamin) {
match = value.match(/\W/g);
match = match && match.length >= nonalphamin;
}
return match;
});
if ($jQval.methods.extension) {
adapters.addSingleVal("accept", "mimtype");
adapters.addSingleVal("extension", "extension");
} else {
// for backward compatibility, when the 'extension' validation method does not exist, such as with versions
// of JQuery Validation plugin prior to 1.10, we should use the 'accept' method for
// validating the extension, and ignore mime-type validations as they are not supported.
adapters.addSingleVal("extension", "extension", "accept");
}
adapters.addSingleVal("regex", "pattern");
adapters.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url");
adapters.addMinMax("length", "minlength", "maxlength", "rangelength").addMinMax("range", "min", "max", "range");
adapters.addMinMax("minlength", "minlength").addMinMax("maxlength", "minlength", "maxlength");
adapters.add("equalto", ["other"], function (options) {
var prefix = getModelPrefix(options.element.name),
other = options.params.other,
fullOtherName = appendModelPrefix(other, prefix),
element = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(fullOtherName) + "']")[0];
setValidationValues(options, "equalTo", element);
});
adapters.add("required", function (options) {
// jQuery Validate equates "required" with "mandatory" for checkbox elements
if (options.element.tagName.toUpperCase() !== "INPUT" || options.element.type.toUpperCase() !== "CHECKBOX") {
setValidationValues(options, "required", true);
}
});
adapters.add("remote", ["url", "type", "additionalfields"], function (options) {
var value = {
url: options.params.url,
type: options.params.type || "GET",
data: {}
},
prefix = getModelPrefix(options.element.name);
$.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) {
var paramName = appendModelPrefix(fieldName, prefix);
value.data[paramName] = function () {
var field = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(paramName) + "']");
// For checkboxes and radio buttons, only pick up values from checked fields.
if (field.is(":checkbox")) {
return field.filter(":checked").val() || field.filter(":hidden").val() || '';
}
else if (field.is(":radio")) {
return field.filter(":checked").val() || '';
}
return field.val();
};
});
setValidationValues(options, "remote", value);
});
adapters.add("password", ["min", "nonalphamin", "regex"], function (options) {
if (options.params.min) {
setValidationValues(options, "minlength", options.params.min);
}
if (options.params.nonalphamin) {
setValidationValues(options, "nonalphamin", options.params.nonalphamin);
}
if (options.params.regex) {
setValidationValues(options, "regex", options.params.regex);
}
});
adapters.add("fileextensions", ["extensions"], function (options) {
setValidationValues(options, "extension", options.params.extensions);
});
$(function () {
$jQval.unobtrusive.parse(document);
});
return $jQval.unobtrusive;
}));

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
=====================
Copyright Jörn Zaefferer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,21 @@
Copyright OpenJS Foundation and other contributors, https://openjsf.org/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

40
compose.yaml Normal file
View File

@@ -0,0 +1,40 @@
services:
postgres:
image: postgres:16-alpine
container_name: postgres-seth
environment:
POSTGRES_PASSWORD: "Password123"
POSTGRES_USER: "postgres"
POSTGRES_DB: "SethRepository"
ports:
- "5432:5432"
volumes:
- ./scripts:/docker-entrypoint-initdb.d
- ./database_data/postgres:/var/lib/postgresql/data
restart: unless-stopped
networks:
seth-repo-network:
ipv4_address: 172.21.100.2
sethrepository:
image: sethrepository
depends_on:
- postgres
build:
context: ./SethRepository
dockerfile: Dockerfile
ports:
- '8080:8080'
networks:
seth-repo-network:
ipv4_address: 172.21.100.3
networks:
seth-repo-network:
driver: bridge
ipam:
driver: default
config:
- subnet: "172.21.100.0/24"
gateway: "172.21.100.1"

7
global.json Normal file
View File

@@ -0,0 +1,7 @@
{
"sdk": {
"version": "8.0.0",
"rollForward": "latestMinor",
"allowPrerelease": false
}
}

35
scripts/init-db.sql Normal file
View File

@@ -0,0 +1,35 @@
SELECT 'CREATE DATABASE "SethRepository"'
WHERE NOT EXISTS (SELECT
FROM pg_database
WHERE datname = 'SethRepository')
\gexec
\connect "SethRepository"
CREATE TABLE IF NOT EXISTS plugins
(
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description VARCHAR(255) NOT NULL,
author VARCHAR(255) NOT NULL,
version VARCHAR(255) NOT NULL,
downloadlink VARCHAR(255) NOT NULL,
operatingsystem INTEGER NOT NULL,
isapproved BOOLEAN NOT NULL DEFAULT FALSE
);
-- dependencies ------------------------------------------------------
CREATE TABLE IF NOT EXISTS dependencies
(
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
downloadlink VARCHAR(255) NOT NULL,
isexecutable BOOLEAN NOT NULL
);
-- link table --------------------------------------------------------
CREATE TABLE IF NOT EXISTS plugindependencieslink
(
id SERIAL PRIMARY KEY,
pluginid INTEGER NOT NULL REFERENCES plugins (id),
dependencyid INTEGER NOT NULL REFERENCES dependencies (id)
);