Added API server
This commit is contained in:
37
src/server/ApiKeyMiddleware.cs
Normal file
37
src/server/ApiKeyMiddleware.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
|
namespace server;
|
||||||
|
|
||||||
|
public class ApiKeyMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
|
public ApiKeyMiddleware(RequestDelegate next, IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InvokeAsync(HttpContext context)
|
||||||
|
{
|
||||||
|
if (!context.Request.Headers.TryGetValue("X-API-KEY", out StringValues extractedApiKey))
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 401;
|
||||||
|
await context.Response.WriteAsync("API Key is missing.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var validApiKeys = _configuration.GetSection("Embeddingsearch").GetSection("ApiKeys").Get<List<string>>();
|
||||||
|
#pragma warning disable CS8604
|
||||||
|
if (validApiKeys == null || !validApiKeys.Contains(extractedApiKey)) // CS8604 extractedApiKey is not null here, but the compiler still thinks that it might be.
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = 403;
|
||||||
|
await context.Response.WriteAsync("Invalid API Key.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#pragma warning restore CS8604
|
||||||
|
|
||||||
|
await _next(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
102
src/server/Controllers/EntityController.cs
Normal file
102
src/server/Controllers/EntityController.cs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using embeddingsearch;
|
||||||
|
using System.Text.Json;
|
||||||
|
using server.Models;
|
||||||
|
|
||||||
|
namespace server.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("[controller]")]
|
||||||
|
public class EntityController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<EntityController> _logger;
|
||||||
|
private readonly IConfiguration _config;
|
||||||
|
private SearchomainManager _domainManager;
|
||||||
|
|
||||||
|
public EntityController(ILogger<EntityController> logger, IConfiguration config, SearchomainManager domainManager)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_config = config;
|
||||||
|
_domainManager = domainManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("Query")]
|
||||||
|
public ActionResult<EntityQueryResults> Query(string searchdomain, string query)
|
||||||
|
{
|
||||||
|
Searchdomain searchdomain_ = _domainManager.GetSearchdomain(searchdomain);
|
||||||
|
var results = searchdomain_.Search(query);
|
||||||
|
List<EntityQueryResult> queryResults = [.. results.Select(r => new EntityQueryResult
|
||||||
|
{
|
||||||
|
Name = r.Item2,
|
||||||
|
Value = r.Item1
|
||||||
|
})];
|
||||||
|
return Ok(new EntityQueryResults(){Results = queryResults});
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("Index")]
|
||||||
|
public ActionResult<EntityIndexResult> Index(string searchdomain, string jsonEntity)
|
||||||
|
{
|
||||||
|
Searchdomain searchdomain_ = _domainManager.GetSearchdomain(searchdomain);
|
||||||
|
List<JSONEntity>? jsonEntities = JsonSerializer.Deserialize<List<JSONEntity>?>(jsonEntity);
|
||||||
|
if (jsonEntities is not null)
|
||||||
|
{
|
||||||
|
|
||||||
|
List<Entity>? entities = searchdomain_.EntitiesFromJSON(jsonEntity);
|
||||||
|
if (entities is not null)
|
||||||
|
{
|
||||||
|
return new EntityIndexResult() {Success = true};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Unable to deserialize an entity");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new EntityIndexResult() {Success = false};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("List")]
|
||||||
|
public ActionResult<EntityListResults> List(string searchdomain)
|
||||||
|
{
|
||||||
|
EntityListResults entityListResults = new() {Results = []};
|
||||||
|
Searchdomain searchdomain_ = _domainManager.GetSearchdomain(searchdomain);
|
||||||
|
foreach (Entity entity in searchdomain_.entityCache)
|
||||||
|
{
|
||||||
|
List<AttributeResult> attributeResults = [];
|
||||||
|
foreach (KeyValuePair<string, string> attribute in entity.attributes)
|
||||||
|
{
|
||||||
|
attributeResults.Add(new AttributeResult() {Name = attribute.Key, Value = attribute.Value});
|
||||||
|
}
|
||||||
|
List<DatapointResult> datapointResults = [];
|
||||||
|
foreach (Datapoint datapoint in entity.datapoints)
|
||||||
|
{
|
||||||
|
List<EmbeddingResult> embeddingResults = [];
|
||||||
|
foreach ((string, float[]) embedding in datapoint.embeddings)
|
||||||
|
{
|
||||||
|
embeddingResults.Add(new EmbeddingResult() {Model = embedding.Item1, Embeddings = embedding.Item2});
|
||||||
|
}
|
||||||
|
datapointResults.Add(new DatapointResult() {Name = datapoint.name, ProbMethod = datapoint.probMethod.Method.Name, Embeddings = embeddingResults});
|
||||||
|
}
|
||||||
|
EntityListResult entityListResult = new()
|
||||||
|
{
|
||||||
|
Name = entity.name,
|
||||||
|
Attributes = attributeResults,
|
||||||
|
Datapoints = datapointResults
|
||||||
|
};
|
||||||
|
entityListResults.Results.Add(entityListResult);
|
||||||
|
}
|
||||||
|
return entityListResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("Delete")]
|
||||||
|
public ActionResult<EntityDeleteResults> Delete(string searchdomain, string entity) // TODO test this
|
||||||
|
{
|
||||||
|
Searchdomain searchdomain_ = _domainManager.GetSearchdomain(searchdomain);
|
||||||
|
Entity? entity_ = searchdomain_.GetEntity(entity);
|
||||||
|
if (entity_ is null)
|
||||||
|
{
|
||||||
|
return new EntityDeleteResults() {Success = false};
|
||||||
|
}
|
||||||
|
searchdomain_.DatabaseRemoveEntity(entity);
|
||||||
|
return new EntityDeleteResults() {Success = true};
|
||||||
|
}
|
||||||
|
}
|
||||||
64
src/server/Controllers/SearchdomainController.cs
Normal file
64
src/server/Controllers/SearchdomainController.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using embeddingsearch;
|
||||||
|
using server.Models;
|
||||||
|
|
||||||
|
namespace server.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("[controller]")]
|
||||||
|
public class SearchdomainController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<SearchdomainController> _logger;
|
||||||
|
private readonly IConfiguration _config;
|
||||||
|
private SearchomainManager _domainManager;
|
||||||
|
|
||||||
|
public SearchdomainController(ILogger<SearchdomainController> logger, IConfiguration config, SearchomainManager domainManager)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_config = config;
|
||||||
|
_domainManager = domainManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("List")]
|
||||||
|
public ActionResult<SearchdomainListResults> List()
|
||||||
|
{
|
||||||
|
return Ok(_domainManager.ListSearchdomains());
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("Create")]
|
||||||
|
public ActionResult<SearchdomainCreateResults> Create(string searchdomain, string settings = "{}")
|
||||||
|
{
|
||||||
|
return Ok(new SearchdomainCreateResults(){Id = _domainManager.CreateSearchdomain(searchdomain, settings)});
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("Delete")]
|
||||||
|
public ActionResult<SearchdomainDeleteResults> Delete(string searchdomain)
|
||||||
|
{
|
||||||
|
bool success;
|
||||||
|
int deletedEntries;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
success = true;
|
||||||
|
deletedEntries = _domainManager.DeleteSearchdomain(searchdomain);
|
||||||
|
} catch (Exception)
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
deletedEntries = 0;
|
||||||
|
}
|
||||||
|
return Ok(new SearchdomainDeleteResults(){Success = success, DeletedEntities = deletedEntries});
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("Update")]
|
||||||
|
public ActionResult<SearchdomainUpdateResults> Update(string searchdomain, string newName, string settings = "{}")
|
||||||
|
{
|
||||||
|
Searchdomain searchdomain_ = _domainManager.GetSearchdomain(searchdomain);
|
||||||
|
Dictionary<string, dynamic> parameters = new()
|
||||||
|
{
|
||||||
|
{"name", newName},
|
||||||
|
{"settings", settings},
|
||||||
|
{"id", searchdomain_.id}
|
||||||
|
};
|
||||||
|
searchdomain_.ExecuteSQLNonQuery("UPDATE searchdomain set name = @name, settings = @settings WHERE id = @id", parameters);
|
||||||
|
return Ok(new SearchdomainUpdateResults(){Success = true});
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/server/Models/EntityModels.cs
Normal file
55
src/server/Models/EntityModels.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
namespace server.Models;
|
||||||
|
|
||||||
|
|
||||||
|
public class EntityQueryResults
|
||||||
|
{
|
||||||
|
public required List<EntityQueryResult> Results { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EntityQueryResult
|
||||||
|
{
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public float Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EntityIndexResult
|
||||||
|
{
|
||||||
|
public required bool Success { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EntityListResults
|
||||||
|
{
|
||||||
|
public required List<EntityListResult> Results { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EntityListResult
|
||||||
|
{
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public required List<AttributeResult> Attributes { get; set; }
|
||||||
|
public required List<DatapointResult> Datapoints { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AttributeResult
|
||||||
|
{
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public required string Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DatapointResult
|
||||||
|
{
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public required string ProbMethod { get; set; }
|
||||||
|
public required List<EmbeddingResult> Embeddings { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EmbeddingResult
|
||||||
|
{
|
||||||
|
public required string Model { get; set; }
|
||||||
|
public required float[] Embeddings { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EntityDeleteResults
|
||||||
|
{
|
||||||
|
public required bool Success { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
24
src/server/Models/SearchdomainModels.cs
Normal file
24
src/server/Models/SearchdomainModels.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
namespace server.Models
|
||||||
|
{
|
||||||
|
public class SearchdomainListResults
|
||||||
|
{
|
||||||
|
public required List<string> Searchdomains { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SearchdomainCreateResults
|
||||||
|
{
|
||||||
|
public required int Id { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SearchdomainUpdateResults
|
||||||
|
{
|
||||||
|
public required bool Success { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SearchdomainDeleteResults
|
||||||
|
{
|
||||||
|
public required bool Success { get; set; }
|
||||||
|
public required int DeletedEntities { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
31
src/server/Program.cs
Normal file
31
src/server/Program.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using server;
|
||||||
|
|
||||||
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// Add services to the container.
|
||||||
|
|
||||||
|
builder.Services.AddControllers();
|
||||||
|
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
|
builder.Services.AddSingleton<SearchomainManager>();
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
// Configure the HTTP request pipeline.
|
||||||
|
if (app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
app.UseMiddleware<ApiKeyMiddleware>();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.MapControllers();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
116
src/server/SearchdomainManager.cs
Normal file
116
src/server/SearchdomainManager.cs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
using embeddingsearch;
|
||||||
|
using MySql.Data.MySqlClient;
|
||||||
|
using System.Data.Common;
|
||||||
|
using OllamaSharp;
|
||||||
|
|
||||||
|
namespace server;
|
||||||
|
|
||||||
|
public class SearchomainManager
|
||||||
|
{
|
||||||
|
private Dictionary<string, Searchdomain> searchdomains = [];
|
||||||
|
private readonly ILogger<SearchomainManager> _logger;
|
||||||
|
private readonly IConfiguration _config;
|
||||||
|
private readonly string ollamaURL;
|
||||||
|
private readonly string connectionString;
|
||||||
|
private OllamaApiClient client;
|
||||||
|
private MySqlConnection connection;
|
||||||
|
|
||||||
|
public SearchomainManager(ILogger<SearchomainManager> logger, IConfiguration config)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_config = config;
|
||||||
|
ollamaURL = _config.GetSection("Embeddingsearch")["OllamaURL"] ?? "";
|
||||||
|
connectionString = _config.GetSection("Embeddingsearch").GetConnectionString("SQL") ?? "";
|
||||||
|
client = new(new Uri(ollamaURL));
|
||||||
|
connection = new MySqlConnection(connectionString);
|
||||||
|
connection.Open();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Searchdomain GetSearchdomain(string searchdomain)
|
||||||
|
{
|
||||||
|
if (searchdomains.TryGetValue(searchdomain, out Searchdomain? value))
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return SetSearchdomain(searchdomain, new Searchdomain(searchdomain, connectionString, client));
|
||||||
|
} catch (MySqlException)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to find the searchdomain {searchdomain}", searchdomain);
|
||||||
|
throw new Exception($"Unable to find the searchdomain {searchdomain}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> ListSearchdomains()
|
||||||
|
{
|
||||||
|
DbDataReader reader = ExecuteSQLCommand("SELECT name FROM searchdomain", []);
|
||||||
|
List<string> results = [];
|
||||||
|
while (reader.Read())
|
||||||
|
{
|
||||||
|
results.Add(reader.GetString(0));
|
||||||
|
}
|
||||||
|
reader.Close();
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CreateSearchdomain(string searchdomain, string settings = "{}")
|
||||||
|
{
|
||||||
|
Dictionary<string, dynamic> parameters = new()
|
||||||
|
{
|
||||||
|
{ "name", searchdomain },
|
||||||
|
{ "settings", settings}
|
||||||
|
};
|
||||||
|
return ExecuteSQLCommandGetInsertedID("INSERT INTO searchdomain (name, settings) VALUES (@name, @settings)", parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int DeleteSearchdomain(string searchdomain)
|
||||||
|
{
|
||||||
|
Searchdomain searchdomain_ = GetSearchdomain(searchdomain);
|
||||||
|
int counter = 0;
|
||||||
|
foreach (Entity entity in searchdomain_.entityCache)
|
||||||
|
{
|
||||||
|
searchdomain_.DatabaseRemoveEntity(entity.name);
|
||||||
|
counter += 1;
|
||||||
|
}
|
||||||
|
_logger.LogDebug($"Number of entities deleted as part of deleting the searchdomain \"{searchdomain}\": {counter}");
|
||||||
|
searchdomain_.ExecuteSQLNonQuery("DELETE FROM entity WHERE id_searchdomain = @id", new() {{"id", searchdomain_.id}}); // Cleanup // TODO add rows affected
|
||||||
|
searchdomain_.ExecuteSQLNonQuery("DELETE FROM searchdomain WHERE name = @name", new() {{"name", searchdomain}});
|
||||||
|
searchdomains.Remove(searchdomain);
|
||||||
|
_logger.LogDebug($"Searchdomain has been successfully removed");
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DbDataReader ExecuteSQLCommand(string query, Dictionary<string, dynamic> parameters)
|
||||||
|
{
|
||||||
|
using MySqlCommand command = connection.CreateCommand();
|
||||||
|
command.CommandText = query;
|
||||||
|
foreach (KeyValuePair<string, dynamic> parameter in parameters)
|
||||||
|
{
|
||||||
|
command.Parameters.AddWithValue($"@{parameter.Key}", parameter.Value);
|
||||||
|
}
|
||||||
|
return command.ExecuteReader();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ExecuteSQLCommandGetInsertedID(string query, Dictionary<string, dynamic> parameters)
|
||||||
|
{
|
||||||
|
using MySqlCommand command = connection.CreateCommand();
|
||||||
|
|
||||||
|
command.CommandText = query;
|
||||||
|
foreach (KeyValuePair<string, dynamic> parameter in parameters)
|
||||||
|
{
|
||||||
|
command.Parameters.AddWithValue($"@{parameter.Key}", parameter.Value);
|
||||||
|
}
|
||||||
|
command.ExecuteNonQuery();
|
||||||
|
command.CommandText = "SELECT LAST_INSERT_ID();";
|
||||||
|
return Convert.ToInt32(command.ExecuteScalar());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Searchdomain SetSearchdomain(string name, Searchdomain searchdomain)
|
||||||
|
{
|
||||||
|
searchdomains[name] = searchdomain;
|
||||||
|
return searchdomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
9
src/server/appsettings.json
Normal file
9
src/server/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
||||||
17
src/server/server.csproj
Normal file
17
src/server/server.csproj
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\embeddingsearch\embeddingsearch.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
Reference in New Issue
Block a user