feat: add comprehensive tagging system with cooccurrence tracking

- Add tags, project_tags, and tag_cooccurrence tables with proper indexes
- Implement full CRUD API endpoints for tag management
- Add tag association endpoints for projects with automatic cooccurrence updates
- Include related tags and project filtering by tag functionality
This commit is contained in:
2026-01-06 02:07:58 -06:00
parent b4c708335b
commit 045781f7a5
25 changed files with 1610 additions and 8 deletions
@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM tag_cooccurrence",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "2487d244576ae4f02e72f74b5a1501040c47a4206c4cfea566f17641ec82d160"
}
@@ -0,0 +1,42 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE tags\n SET slug = $2, name = $3\n WHERE id = $1\n RETURNING id, slug, name, created_at\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "slug",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "created_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Uuid",
"Text",
"Text"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "2f868a87ff4c32270ca92447e230e6a875b81907877498329c3664dc41cc7a08"
}
@@ -0,0 +1,40 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT id, slug, name, created_at\n FROM tags\n WHERE id = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "slug",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "created_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "300dd247ebbca159f76da9e506c99e838bf7bd5114f645facb573f0df3f80807"
}
@@ -0,0 +1,40 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT t.id, t.slug, t.name, t.created_at\n FROM tags t\n JOIN project_tags pt ON t.id = pt.tag_id\n WHERE pt.project_id = $1\n ORDER BY t.name ASC\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "slug",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "created_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "31c6f77f585132105fd8da6aad396337fdd24165794cadba6a2aa4fa3663dc28"
}
@@ -0,0 +1,44 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT \n t.id, \n t.slug, \n t.name, \n t.created_at,\n COUNT(pt.project_id)::int as \"project_count!\"\n FROM tags t\n LEFT JOIN project_tags pt ON t.id = pt.tag_id\n GROUP BY t.id, t.slug, t.name, t.created_at\n ORDER BY t.name ASC\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "slug",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 4,
"name": "project_count!",
"type_info": "Int4"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false,
false,
false,
null
]
},
"hash": "4ce09c53c094bc94294cfd14e4a696b4cc118bf950b287132718fb02ec208879"
}
@@ -0,0 +1,47 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT \n t.id, \n t.slug, \n t.name, \n t.created_at,\n tc.count\n FROM tag_cooccurrence tc\n JOIN tags t ON (tc.tag_a = t.id OR tc.tag_b = t.id)\n WHERE (tc.tag_a = $1 OR tc.tag_b = $1) AND t.id != $1\n ORDER BY tc.count DESC, t.name ASC\n LIMIT $2\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "slug",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 4,
"name": "count",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Uuid",
"Int8"
]
},
"nullable": [
false,
false,
false,
false,
false
]
},
"hash": "5564b47c730abd5436ee2836aa684054e709357b1993529f3b12a1597d0a1b32"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO project_tags (project_id, tag_id) VALUES ($1, $2)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Uuid"
]
},
"nullable": []
},
"hash": "5f26572b23588a5a96cb027b5517d75eb8a12c4d6123bf72b966de5662f4b2d4"
}
@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT EXISTS(SELECT 1 FROM tags WHERE LOWER(name) = LOWER($1)) as \"exists!\"\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists!",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "778619a2d55b7e311f9d8bfd75335a1605065f136bf350b030d1b86d3cc92b37"
}
@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT id FROM projects WHERE slug = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false
]
},
"hash": "7f264699f8897371b3f1a36f7908a1652a256537b66fe9a4452be82afcb0d7bf"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO project_tags (project_id, tag_id)\n VALUES ($1, $2)\n ON CONFLICT (project_id, tag_id) DO NOTHING\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Uuid"
]
},
"nullable": []
},
"hash": "7f285ead462f3247e5c2e99f45188b6d3e8d5376776a9b6e815b1a6e223d55cf"
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO tags (slug, name)\n VALUES ($1, $2)\n RETURNING id\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false
]
},
"hash": "7f51a4655f0c523554b1e57fd70cfd7d8930909c64495b2ae1c1f44f905ce44e"
}
@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT EXISTS(SELECT 1 FROM tags WHERE slug = $1) as \"exists!\"\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists!",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "a55f7d64fc0f41f150663aae82ac6fbcabf90d2627d194c3fb23f80c24dcc57a"
}
@@ -0,0 +1,40 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT id, slug, name, created_at\n FROM tags\n WHERE slug = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "slug",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "created_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "baffa1674306622fbc4cb3cae9e351517f4d595c11364ec797666bab05444120"
}
@@ -0,0 +1,100 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT \n p.id, \n p.slug, \n p.title, \n p.description, \n p.status as \"status: ProjectStatus\", \n p.github_repo, \n p.demo_url, \n p.priority, \n p.icon, \n p.last_github_activity, \n p.created_at, \n p.updated_at\n FROM projects p\n JOIN project_tags pt ON p.id = pt.project_id\n WHERE pt.tag_id = $1\n ORDER BY p.priority DESC, p.created_at DESC\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "slug",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "title",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "description",
"type_info": "Text"
},
{
"ordinal": 4,
"name": "status: ProjectStatus",
"type_info": {
"Custom": {
"name": "project_status",
"kind": {
"Enum": [
"active",
"maintained",
"archived",
"hidden"
]
}
}
}
},
{
"ordinal": 5,
"name": "github_repo",
"type_info": "Text"
},
{
"ordinal": 6,
"name": "demo_url",
"type_info": "Text"
},
{
"ordinal": 7,
"name": "priority",
"type_info": "Int4"
},
{
"ordinal": 8,
"name": "icon",
"type_info": "Text"
},
{
"ordinal": 9,
"name": "last_github_activity",
"type_info": "Timestamptz"
},
{
"ordinal": 10,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 11,
"name": "updated_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
false,
false,
false,
false,
false,
true,
true,
false,
true,
true,
false,
false
]
},
"hash": "c7dd8cd6c4e50e1e0f327c040a9497d8427848e8754778fbaf5f22e656e04e12"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM project_tags WHERE project_id = $1 AND tag_id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Uuid"
]
},
"nullable": []
},
"hash": "d449cad77fa2e65e2f4d1eec62daa09045e903648e615083500f0f373c4f3525"
}
@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO tag_cooccurrence (tag_a, tag_b, count)\n SELECT \n LEAST(t1.tag_id, t2.tag_id) as tag_a,\n GREATEST(t1.tag_id, t2.tag_id) as tag_b,\n COUNT(*)::int as count\n FROM project_tags t1\n JOIN project_tags t2 ON t1.project_id = t2.project_id\n WHERE t1.tag_id < t2.tag_id\n GROUP BY tag_a, tag_b\n HAVING COUNT(*) > 0\n ",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "d988a5efcea128fd29c9476b5ea995116e81b7f2b86dd39e33a90063130c4650"
}
@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM tags WHERE id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": []
},
"hash": "dd0d0e3fd03f130aab947d13580796eee9a786e2ca01d339fd0e8356f8ad3824"
}
@@ -0,0 +1,41 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO tags (slug, name)\n VALUES ($1, $2)\n RETURNING id, slug, name, created_at\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "slug",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "created_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "de9ba22c6a50b35761097abc410d22a6c5e6c307bca8d7ba8d2aa5a08c4ce91f"
}
@@ -0,0 +1,38 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT id, slug, name, created_at\n FROM tags\n ORDER BY name ASC\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Uuid"
},
{
"ordinal": 1,
"name": "slug",
"type_info": "Text"
},
{
"ordinal": 2,
"name": "name",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "created_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": []
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "df4b8594ebbe7c0cfdfaa03a954931d50e87bb52bc4131c0b4803a57688d19ff"
}