search for standard sites pub-search.waow.tech
search zig blog atproto
at multi-platform-schema 128 lines 4.5 kB view raw
1const std = @import("std"); 2const json = std.json; 3const Allocator = std.mem.Allocator; 4const zql = @import("zql"); 5const db = @import("db/mod.zig"); 6const activity = @import("activity.zig"); 7 8const TagJson = struct { tag: []const u8, count: i64 }; 9const PopularJson = struct { query: []const u8, count: i64 }; 10 11const TagsQuery = zql.Query( 12 \\SELECT tag, COUNT(*) as count 13 \\FROM document_tags 14 \\GROUP BY tag 15 \\ORDER BY count DESC 16 \\LIMIT 100 17); 18 19pub fn getTags(alloc: Allocator) ![]const u8 { 20 const c = db.getClient() orelse return error.NotInitialized; 21 22 var output: std.Io.Writer.Allocating = .init(alloc); 23 errdefer output.deinit(); 24 25 var res = c.query(TagsQuery.positional, &.{}) catch { 26 try output.writer.writeAll("{\"error\":\"failed to fetch tags\"}"); 27 return try output.toOwnedSlice(); 28 }; 29 defer res.deinit(); 30 31 var jw: json.Stringify = .{ .writer = &output.writer }; 32 try jw.beginArray(); 33 for (res.rows) |row| try jw.write(TagJson{ .tag = row.text(0), .count = row.int(1) }); 34 try jw.endArray(); 35 return try output.toOwnedSlice(); 36} 37 38pub const Stats = struct { 39 documents: i64, 40 publications: i64, 41 searches: i64, 42 errors: i64, 43 started_at: i64, 44 cache_hits: i64, 45 cache_misses: i64, 46}; 47 48pub fn getStats() Stats { 49 const c = db.getClient() orelse return .{ .documents = 0, .publications = 0, .searches = 0, .errors = 0, .started_at = 0, .cache_hits = 0, .cache_misses = 0 }; 50 51 var res = c.query( 52 \\SELECT 53 \\ (SELECT COUNT(*) FROM documents) as docs, 54 \\ (SELECT COUNT(*) FROM publications) as pubs, 55 \\ (SELECT total_searches FROM stats WHERE id = 1) as searches, 56 \\ (SELECT total_errors FROM stats WHERE id = 1) as errors, 57 \\ (SELECT service_started_at FROM stats WHERE id = 1) as started_at, 58 \\ (SELECT COALESCE(cache_hits, 0) FROM stats WHERE id = 1) as cache_hits, 59 \\ (SELECT COALESCE(cache_misses, 0) FROM stats WHERE id = 1) as cache_misses 60 , &.{}) catch return .{ .documents = 0, .publications = 0, .searches = 0, .errors = 0, .started_at = 0, .cache_hits = 0, .cache_misses = 0 }; 61 defer res.deinit(); 62 63 const row = res.first() orelse return .{ .documents = 0, .publications = 0, .searches = 0, .errors = 0, .started_at = 0, .cache_hits = 0, .cache_misses = 0 }; 64 return .{ 65 .documents = row.int(0), 66 .publications = row.int(1), 67 .searches = row.int(2), 68 .errors = row.int(3), 69 .started_at = row.int(4), 70 .cache_hits = row.int(5), 71 .cache_misses = row.int(6), 72 }; 73} 74 75pub fn recordSearch(query: []const u8) void { 76 const c = db.getClient() orelse return; 77 78 activity.record(); 79 c.exec("UPDATE stats SET total_searches = total_searches + 1 WHERE id = 1", &.{}) catch {}; 80 81 // track popular searches (skip empty/very short queries) 82 if (query.len >= 2) { 83 c.exec( 84 "INSERT INTO popular_searches (query, count) VALUES (?, 1) ON CONFLICT(query) DO UPDATE SET count = count + 1", 85 &.{query}, 86 ) catch {}; 87 } 88} 89 90pub fn recordError() void { 91 const c = db.getClient() orelse return; 92 c.exec("UPDATE stats SET total_errors = total_errors + 1 WHERE id = 1", &.{}) catch {}; 93} 94 95pub fn recordCacheHit() void { 96 const c = db.getClient() orelse return; 97 c.exec("UPDATE stats SET cache_hits = COALESCE(cache_hits, 0) + 1 WHERE id = 1", &.{}) catch {}; 98} 99 100pub fn recordCacheMiss() void { 101 const c = db.getClient() orelse return; 102 c.exec("UPDATE stats SET cache_misses = COALESCE(cache_misses, 0) + 1 WHERE id = 1", &.{}) catch {}; 103} 104 105pub fn getPopular(alloc: Allocator, limit: usize) ![]const u8 { 106 const c = db.getClient() orelse return error.NotInitialized; 107 108 var output: std.Io.Writer.Allocating = .init(alloc); 109 errdefer output.deinit(); 110 111 var buf: [8]u8 = undefined; 112 const limit_str = std.fmt.bufPrint(&buf, "{d}", .{limit}) catch "3"; 113 114 var res = c.query( 115 "SELECT query, count FROM popular_searches ORDER BY count DESC LIMIT ?", 116 &.{limit_str}, 117 ) catch { 118 try output.writer.writeAll("[]"); 119 return try output.toOwnedSlice(); 120 }; 121 defer res.deinit(); 122 123 var jw: json.Stringify = .{ .writer = &output.writer }; 124 try jw.beginArray(); 125 for (res.rows) |row| try jw.write(PopularJson{ .query = row.text(0), .count = row.int(1) }); 126 try jw.endArray(); 127 return try output.toOwnedSlice(); 128}