Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/DevBrain.Functions/Services/CosmosDocumentStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,25 @@ private static string NormalizeForHash(string content) =>
return null;
}

public async Task<int> TouchAllAsync()
{
var queryDefinition = new QueryDefinition("SELECT * FROM c");
var touched = 0;

using var iterator = _container.GetItemQueryIterator<BrainDocument>(queryDefinition);
while (iterator.HasMoreResults)
{
var response = await iterator.ReadNextAsync();
foreach (var document in response)
{
await UpsertAsync(document);
touched++;
}
}

return touched;
}

public async Task<IReadOnlyList<BrainDocument>> ListAsync(string project, string? prefix = null)
{
var queryText = prefix is not null
Expand Down
7 changes: 7 additions & 0 deletions src/DevBrain.Functions/Services/IDocumentStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ public interface IDocumentStore
/// </summary>
Task<BrainDocument?> GetMetadataAsync(string key, string project);

/// <summary>
/// Re-upserts every document in the store, triggering server-side metadata
/// recomputation (contentHash, contentLength). Returns the number of documents
/// touched. Intended as a one-shot backfill after adding new computed fields.
/// </summary>
Task<int> TouchAllAsync();

/// <summary>
/// Deletes a single document by key within a project. Idempotent: returns false
/// when the document does not exist. Accepts both colon and slash keys to support
Expand Down
40 changes: 40 additions & 0 deletions src/DevBrain.Functions/Tools/AdminFunctions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Net;
using DevBrain.Functions.Services;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;

namespace DevBrain.Functions.Tools;

/// <summary>
/// Administrative HTTP endpoints that are not exposed as MCP tools.
/// Gated by <see cref="AuthorizationLevel.Admin"/> (requires the Function App master key).
/// </summary>
public sealed class AdminFunctions
{
private readonly IDocumentStore _store;

public AdminFunctions(IDocumentStore store)
{
_store = store;
}

/// <summary>
/// Re-upserts every document to backfill computed metadata fields (contentHash,
/// contentLength). Idempotent — safe to run multiple times. Not exposed as an
/// MCP tool; invoke via HTTP with the Function App master key.
/// </summary>
[Function(nameof(TouchAllDocuments))]
public async Task<HttpResponseData> TouchAllDocuments(
[HttpTrigger(AuthorizationLevel.Admin, "post", Route = "admin/touch")] HttpRequestData req)
{
var touched = await _store.TouchAllAsync();

var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(new
{
touched,
message = $"Re-upserted {touched} document(s). contentHash and contentLength are now populated."
});
return response;
}
}
Loading