Coverage Report

Created: 2026-03-25 23:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/runner/work/tenet/tenet/tenet-api/src/routes/projects/skills.rs
Line
Count
Source
1
use axum::{
2
    Json, Router,
3
    extract::{Query, State},
4
    routing::{delete, get, post},
5
};
6
use serde::Deserialize;
7
use tenet_aws::models::skill::{
8
    CreateSkillRequest, ImportSkillRequest, LinkSkillRequest, SkillId, UpdateSkillRequest,
9
};
10
11
use crate::http::{AppState, JsonResponse, ProjectRequest};
12
use crate::routes::policy::require_org_admin;
13
14
use super::shared::{ProjectPath, SkillLinkPath, SkillPath};
15
16
#[derive(Debug, Deserialize)]
17
struct ListSkillQuery {
18
    parent_skill_id: Option<SkillId>,
19
}
20
21
2
pub fn router() -> Router<AppState> {
22
2
    Router::new()
23
2
        .route(
24
2
            "/organisations/{org_id}/projects/{project_id}/skills",
25
2
            get(list_skills).post(create_skill),
26
        )
27
2
        .route(
28
2
            "/organisations/{org_id}/projects/{project_id}/skills/import",
29
2
            post(import_skill),
30
        )
31
2
        .route(
32
2
            "/organisations/{org_id}/projects/{project_id}/skills/{skill_id}",
33
2
            get(get_skill).patch(update_skill).delete(delete_skill),
34
        )
35
2
        .route(
36
2
            "/organisations/{org_id}/projects/{project_id}/skills/{skill_id}/reimport",
37
2
            post(reimport_skill),
38
        )
39
2
        .route(
40
2
            "/organisations/{org_id}/projects/{project_id}/skills/{skill_id}/links",
41
2
            post(link_skill),
42
        )
43
2
        .route(
44
2
            "/organisations/{org_id}/projects/{project_id}/skills/{skill_id}/links/{link_id}",
45
2
            delete(unlink_skill),
46
        )
47
2
}
48
49
0
async fn list_skills(
50
0
    State(state): State<AppState>,
51
0
    req: ProjectRequest<ProjectPath>,
52
0
    Query(query): Query<ListSkillQuery>,
53
0
) -> axum::response::Response {
54
0
    let ProjectRequest { user: _, path } = req;
55
0
    JsonResponse::ok_result_with_context(
56
0
        state
57
0
            .skill
58
0
            .list_skills(
59
0
                &path.org_id,
60
0
                &path.project_id,
61
0
                query.parent_skill_id.as_ref(),
62
0
            )
63
0
            .await,
64
        "Failed to list skills",
65
    )
66
0
}
67
68
0
async fn create_skill(
69
0
    State(state): State<AppState>,
70
0
    req: ProjectRequest<ProjectPath>,
71
0
    Json(body): Json<CreateSkillRequest>,
72
0
) -> axum::response::Response {
73
0
    let ProjectRequest { user: _, path } = req;
74
0
    JsonResponse::created_result_with_context(
75
0
        state
76
0
            .skill
77
0
            .create_skill(&path.org_id, &path.project_id, &body)
78
0
            .await,
79
        "Failed to create skill",
80
    )
81
0
}
82
83
0
async fn import_skill(
84
0
    State(state): State<AppState>,
85
0
    req: ProjectRequest<ProjectPath>,
86
0
    Json(body): Json<ImportSkillRequest>,
87
0
) -> axum::response::Response {
88
0
    let ProjectRequest { user: _, path } = req;
89
0
    JsonResponse::created_result_with_context(
90
0
        state
91
0
            .skill
92
0
            .import_skill(&path.org_id, &path.project_id, &body)
93
0
            .await,
94
        "Failed to import skill",
95
    )
96
0
}
97
98
0
async fn get_skill(
99
0
    State(state): State<AppState>,
100
0
    req: ProjectRequest<SkillPath>,
101
0
) -> axum::response::Response {
102
0
    let ProjectRequest { user: _, path } = req;
103
0
    JsonResponse::ok_result_with_context(
104
0
        state
105
0
            .skill
106
0
            .get_skill(&path.org_id, &path.project_id, &path.skill_id)
107
0
            .await,
108
        "Failed to get skill",
109
    )
110
0
}
111
112
0
async fn update_skill(
113
0
    State(state): State<AppState>,
114
0
    req: ProjectRequest<SkillPath>,
115
0
    Json(body): Json<UpdateSkillRequest>,
116
0
) -> axum::response::Response {
117
0
    let ProjectRequest { user: _, path } = req;
118
0
    JsonResponse::ok_result_with_context(
119
0
        state
120
0
            .skill
121
0
            .update_skill(&path.org_id, &path.project_id, &path.skill_id, body)
122
0
            .await,
123
        "Failed to update skill",
124
    )
125
0
}
126
127
0
async fn delete_skill(
128
0
    State(state): State<AppState>,
129
0
    req: ProjectRequest<SkillPath>,
130
0
) -> axum::response::Response {
131
0
    let ProjectRequest { user, path } = req;
132
0
    if let Err(err) = require_org_admin(&state, &path.org_id, &user.id).await {
133
0
        return JsonResponse::error(err);
134
0
    }
135
0
    JsonResponse::ok_result_with_context(
136
0
        state
137
0
            .skill
138
0
            .delete_skill(&path.org_id, &path.project_id, &path.skill_id)
139
0
            .await,
140
        "Failed to delete skill",
141
    )
142
0
}
143
144
0
async fn reimport_skill(
145
0
    State(state): State<AppState>,
146
0
    req: ProjectRequest<SkillPath>,
147
0
) -> axum::response::Response {
148
0
    let ProjectRequest { user: _, path } = req;
149
0
    JsonResponse::ok_result_with_context(
150
0
        state
151
0
            .skill
152
0
            .reimport_skill(&path.org_id, &path.project_id, &path.skill_id)
153
0
            .await,
154
        "Failed to reimport skill",
155
    )
156
0
}
157
158
0
async fn link_skill(
159
0
    State(state): State<AppState>,
160
0
    req: ProjectRequest<SkillPath>,
161
0
    Json(body): Json<LinkSkillRequest>,
162
0
) -> axum::response::Response {
163
0
    let ProjectRequest { user, path } = req;
164
0
    JsonResponse::created_result_with_context(
165
0
        state
166
0
            .skill
167
0
            .link_skill(
168
0
                &path.org_id,
169
0
                &path.project_id,
170
0
                &path.skill_id,
171
0
                &body.target_id,
172
0
                body.target_kind,
173
0
                &user.id,
174
0
            )
175
0
            .await,
176
        "Failed to link skill",
177
    )
178
0
}
179
180
0
async fn unlink_skill(
181
0
    State(state): State<AppState>,
182
0
    req: ProjectRequest<SkillLinkPath>,
183
0
) -> axum::response::Response {
184
0
    let ProjectRequest { user, path } = req;
185
0
    JsonResponse::no_content_result_with_context(
186
0
        state
187
0
            .skill
188
0
            .unlink_skill(
189
0
                &path.org_id,
190
0
                &path.project_id,
191
0
                &path.skill_id,
192
0
                &path.link_id,
193
0
                &user.id,
194
0
            )
195
0
            .await,
196
        "Failed to unlink skill",
197
    )
198
0
}