Fixed Server folder name

This commit is contained in:
2025-06-05 05:52:36 +00:00
parent b93b14f051
commit 183a150234
10 changed files with 0 additions and 0 deletions

View 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);
}
}

View File

@@ -0,0 +1,135 @@
using Microsoft.AspNetCore.Mvc;
using embeddingsearch;
using System.Text.Json;
using Models;
using System.Text.Json.Nodes;
namespace server.Controllers;
[ApiController]
[Route("[controller]")]
public class EntityController : ControllerBase
{
private readonly ILogger<EntityController> _logger;
private readonly IConfiguration _config;
private SearchdomainManager _domainManager;
public EntityController(ILogger<EntityController> logger, IConfiguration config, SearchdomainManager domainManager)
{
_logger = logger;
_config = config;
_domainManager = domainManager;
}
[HttpGet("Query")]
public ActionResult<EntityQueryResults> Query(string searchdomain, string query)
{
Searchdomain searchdomain_;
try
{
searchdomain_ = _domainManager.GetSearchdomain(searchdomain);
} catch (Exception)
{
return Ok(new EntityQueryResults() {Results = []});
}
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});
}
[HttpPost("Index")]
public ActionResult<EntityIndexResult> Index(string searchdomain, [FromBody] List<JSONEntity>? jsonEntity)
{
Searchdomain searchdomain_;
try
{
searchdomain_ = _domainManager.GetSearchdomain(searchdomain);
}
catch (Exception)
{
return Ok(new EntityIndexResult() { Success = false });
}
List<Entity>? entities = searchdomain_.EntitiesFromJSON(JsonSerializer.Serialize(jsonEntity));
if (entities is not null)
{
_domainManager.InvalidateSearchdomainCache(searchdomain);
return Ok(new EntityIndexResult() { Success = true });
}
else
{
_logger.LogDebug("Unable to deserialize an entity");
}
return Ok(new EntityIndexResult() { Success = false });
}
[HttpGet("List")]
public ActionResult<EntityListResults> List(string searchdomain, bool returnEmbeddings = false)
{
Searchdomain searchdomain_;
try
{
searchdomain_ = _domainManager.GetSearchdomain(searchdomain);
} catch (Exception)
{
return Ok(new EntityListResults() {Results = [], Success = false});
}
EntityListResults entityListResults = new() {Results = [], Success = true};
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)
{
if (returnEmbeddings)
{
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});
}
else
{
datapointResults.Add(new DatapointResult() {Name = datapoint.name, ProbMethod = datapoint.probMethod.Method.Name, Embeddings = null});
}
}
EntityListResult entityListResult = new()
{
Name = entity.name,
Attributes = attributeResults,
Datapoints = datapointResults
};
entityListResults.Results.Add(entityListResult);
}
return Ok(entityListResults);
}
[HttpGet("Delete")]
public ActionResult<EntityDeleteResults> Delete(string searchdomain, string entityName) // TODO test this
{
Searchdomain searchdomain_;
try
{
searchdomain_ = _domainManager.GetSearchdomain(searchdomain);
} catch (Exception)
{
return Ok(new EntityDeleteResults() {Success = false});
}
Entity? entity_ = searchdomain_.GetEntity(entityName);
if (entity_ is null)
{
return Ok(new EntityDeleteResults() {Success = false});
}
searchdomain_.RemoveEntity(entityName);
return Ok(new EntityDeleteResults() {Success = true});
}
}

View File

@@ -0,0 +1,81 @@
using Microsoft.AspNetCore.Mvc;
using embeddingsearch;
using Models;
namespace server.Controllers;
[ApiController]
[Route("[controller]")]
public class SearchdomainController : ControllerBase
{
private readonly ILogger<SearchdomainController> _logger;
private readonly IConfiguration _config;
private SearchdomainManager _domainManager;
public SearchdomainController(ILogger<SearchdomainController> logger, IConfiguration config, SearchdomainManager domainManager)
{
_logger = logger;
_config = config;
_domainManager = domainManager;
}
[HttpGet("List")]
public ActionResult<SearchdomainListResults> List()
{
var results = _domainManager.ListSearchdomains()
?? throw new Exception("Unable to list searchdomains");
SearchdomainListResults searchdomainListResults = new() {Searchdomains = results};
return Ok(searchdomainListResults);
}
[HttpGet("Create")]
public ActionResult<SearchdomainCreateResults> Create(string searchdomain, string settings = "{}")
{
try
{
int id = _domainManager.CreateSearchdomain(searchdomain, settings);
return Ok(new SearchdomainCreateResults(){Id = id, Success = true});
} catch (Exception)
{
return Ok(new SearchdomainCreateResults(){Id = null, Success = false});
}
}
[HttpGet("Delete")]
public ActionResult<SearchdomainDeleteResults> Delete(string searchdomain)
{
bool success;
int deletedEntries;
try
{
success = true;
deletedEntries = _domainManager.DeleteSearchdomain(searchdomain);
} catch (Exception ex)
{
Console.WriteLine(ex);
success = false;
deletedEntries = 0;
}
return Ok(new SearchdomainDeleteResults(){Success = success, DeletedEntities = deletedEntries});
}
[HttpGet("Update")]
public ActionResult<SearchdomainUpdateResults> Update(string searchdomain, string newName, string settings = "{}")
{
try
{
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);
} catch (Exception)
{
return Ok(new SearchdomainUpdateResults(){Success = false});
}
return Ok(new SearchdomainUpdateResults(){Success = true});
}
}

View File

@@ -0,0 +1,14 @@
namespace server.Exceptions;
public class ServerConfigurationException : Exception
{
public ServerConfigurationException()
: base("Configuration is incomplete or was set up incorrectly")
{
}
public ServerConfigurationException(string message)
: base(message)
{
}
}

31
src/Server/Program.cs Normal file
View 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<SearchdomainManager>();
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();

View File

@@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:23614",
"sslPort": 44354
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5146",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7099;http://localhost:5146",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,130 @@
using embeddingsearch;
using MySql.Data.MySqlClient;
using System.Data.Common;
using OllamaSharp;
using Microsoft.IdentityModel.Tokens;
using server.Exceptions;
namespace server;
public class SearchdomainManager
{
private Dictionary<string, Searchdomain> searchdomains = [];
private readonly ILogger<SearchdomainManager> _logger;
private readonly IConfiguration _config;
private readonly string ollamaURL;
private readonly string connectionString;
private OllamaApiClient client;
private MySqlConnection connection;
public SearchdomainManager(ILogger<SearchdomainManager> logger, IConfiguration config)
{
_logger = logger;
_config = config;
ollamaURL = _config.GetSection("Embeddingsearch")["OllamaURL"] ?? "";
connectionString = _config.GetSection("Embeddingsearch").GetConnectionString("SQL") ?? "";
if (ollamaURL.IsNullOrEmpty() || connectionString.IsNullOrEmpty())
{
throw new ServerConfigurationException("Ollama URL or connection string is empty");
}
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 void InvalidateSearchdomainCache(string searchdomain)
{
searchdomains.Remove(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 = "{}")
{
if (searchdomains.TryGetValue(searchdomain, out Searchdomain? value))
{
throw new Exception("Searchdomain already exists"); // TODO create proper SearchdomainAlreadyExists exception
}
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;
while (searchdomain_.entityCache.Count > 0)
{
searchdomain_.RemoveEntity(searchdomain_.entityCache.First().name);
counter += 1;
}
_logger.LogDebug($"Number of entities deleted as part of deleting the searchdomain \"{searchdomain}\": {counter}");
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;
}
}

View File

@@ -0,0 +1,24 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.AspNetCore": "Warning"
}
},
"Kestrel":{
"Endpoints": {
"http":{
"Url": "http://localhost:5146"
}
}
},
"Embeddingsearch": {
"ConnectionStrings": {
"SQL": "server=localhost;database=embeddingsearch;uid=embeddingsearch;pwd=somepassword!;"
},
"OllamaURL": "http://localhost:11434",
"ApiKeys": ["Some UUID here", "Another UUID here"],
"UseHttpsRedirection": true
}
}

View File

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

18
src/Server/server.csproj Normal file
View File

@@ -0,0 +1,18 @@
<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" />
<ProjectReference Include="..\Models\Models.csproj" />
</ItemGroup>
</Project>