1use ftml_uris::ArchiveId;
2use leptos::prelude::*;
3use std::num::NonZeroU32;
4
5use crate::GitState;
6
7#[server(prefix = "/api/gitlab", endpoint = "get_archives")]
8pub async fn get_archives()
9-> Result<Vec<(flams_backend_types::git::Project, ArchiveId, GitState)>, ServerFnError<String>> {
10 server::get_archives().await
11}
12
13#[server(prefix = "/api/gitlab", endpoint = "get_branches")]
14pub async fn get_branches(
15 id: u64,
16) -> Result<Vec<flams_backend_types::git::Branch>, ServerFnError<String>> {
17 let (oauth, secret) = flams_router_base::get_oauth()?;
18 oauth
19 .get_branches(id, secret)
20 .await
21 .map_err(|e| ServerFnError::WrappedServerError(e.to_string()))
22}
23
24#[server(prefix = "/api/gitlab", endpoint = "update_from_branch")]
25pub async fn update_from_branch(
26 id: Option<NonZeroU32>,
27 archive: ArchiveId,
28 url: String,
29 branch: String,
30) -> Result<(usize, NonZeroU32), ServerFnError<String>> {
31 server::update_from_branch(id, archive, url, branch).await
32}
33
34#[server(prefix = "/api/gitlab", endpoint = "clone_to_queue")]
35pub async fn clone_to_queue(
36 id: Option<NonZeroU32>,
37 archive: ArchiveId,
38 url: String,
39 branch: String,
40 has_release: bool,
41) -> Result<(usize, NonZeroU32), ServerFnError<String>> {
42 server::clone_to_queue(id, archive, url, branch, has_release).await
43}
44
45#[server(prefix = "/api/gitlab", endpoint = "get_new_commits")]
46pub async fn get_new_commits(
47 queue: Option<NonZeroU32>,
48 id: ArchiveId,
49) -> Result<Vec<(String, flams_backend_types::git::Commit)>, ServerFnError<String>> {
50 server::get_new_commits(queue, id).await
51}
52
53#[cfg(feature = "ssr")]
54mod server {
55 use flams_router_base::{LoginState, get_oauth};
56 use flams_router_buildqueue_base::LoginQueue;
57 use flams_utils::{impossible, unwrap};
58 use flams_web_utils::blocking_server_fn;
59 use ftml_uris::ArchiveId;
60 use leptos::prelude::*;
61 use std::num::NonZeroU32;
62
63 use crate::GitState;
64
65 #[allow(clippy::too_many_lines)]
66 pub(super) async fn get_archives()
67 -> Result<Vec<(flams_backend_types::git::Project, ArchiveId, GitState)>, ServerFnError<String>>
68 {
69 use flams_git::gl::auth::GitLabOAuth;
70 use flams_math_archives::backend::{AnyBackend, GlobalBackend, SandboxedRepository};
71 use leptos::either::Either::{Left, Right};
72 async fn get(
73 oauth: GitLabOAuth,
74 secret: String,
75 p: flams_backend_types::git::Project,
76 ) -> (
77 flams_backend_types::git::Project,
78 Result<Option<ArchiveId>, flams_git::gl::Err>,
79 ) {
80 let id = if let Some(b) = &p.default_branch {
81 oauth.get_archive_id(p.id, secret, b).await
82 } else {
83 return (p, Ok(None));
84 };
85 (p, id)
86 }
87 let (oauth, secret) = get_oauth()?;
88 let r = oauth
89 .get_projects(secret.clone())
90 .await
91 .map_err(|e| ServerFnError::WrappedServerError(e.to_string()))?;
92 let mut r2 = Vec::new();
93 let mut js = tokio::task::JoinSet::new();
94 for p in r {
95 js.spawn(get(oauth.clone(), secret.clone(), p));
96 }
97 while let Some(r) = js.join_next().await {
98 match r {
99 Err(e) => return Err(e.to_string().into()),
100 Ok((p, Err(e))) => {
101 tracing::error!("error obtaining archive ID of {} ({}): {e}", p.name, p.id);
102 }
103 Ok((p, Ok(Some(id)))) => r2.push((p, id)),
104 Ok((_, Ok(None))) => (),
105 }
106 }
107 let r = blocking_server_fn(move || {
108 use flams_math_archives::Archive;
109 use flams_math_archives::MathArchive;
110 use flams_system::building::queue_manager::QueueManager;
111
112 let mut ret = Vec::new();
113 let backend = GlobalBackend;
114 let gitlab_url = unwrap!(flams_system::settings::Settings::get().gitlab_url.as_ref());
115 for a in backend.all_archives().iter() {
116 if let Archive::Local(a) = a
117 && let Some((p, id)) = r2
118 .iter()
119 .position(|(_, id)| id == a.id())
120 .map(|i| r2.swap_remove(i))
121 {
122 if let Ok(git) = flams_git::repos::GitRepo::open(a.path()) {
123 if gitlab_url
124 .host_str()
125 .is_some_and(|s| git.is_managed(s).is_some())
126 {
127 ret.push((p, id, Left(git)));
128 } else {
129 ret.push((p, id, Right(GitState::None)));
130 }
131 } else {
132 ret.push((p, id, Right(GitState::None)));
133 }
134 }
135 }
136 ret.extend(r2.into_iter().map(|(p, id)| (p, id, Right(GitState::None))));
137 QueueManager::get().with_all_queues(|qs| {
139 for (qid, q) in qs {
140 if let AnyBackend::Sandbox(sb) = q.backend() {
141 sb.with_repos(|rs| {
142 for r in rs {
143 match r {
144 SandboxedRepository::Git {
145 id: rid, commit, ..
146 } => {
147 if let Some(e) = ret.iter_mut().find_map(|(_, id, e)| {
148 if id == rid { Some(e) } else { None }
149 }) {
150 *e = Right(GitState::Queued {
151 commit: commit.id.clone(),
152 queue: (*qid).into(),
153 });
154 }
155 }
157 SandboxedRepository::Copy(_) => (),
158 }
159 }
160 });
161 }
162 }
163 });
164
165 Ok(ret)
166 })
167 .await?;
168
169 let mut r2 = Vec::new();
170
171 let mut js = tokio::task::JoinSet::new();
172 for (p, id, e) in r {
173 match e {
174 Right(e) => r2.push((p, id, e)),
175 Left(git) => {
176 let secret = secret.clone();
177 js.spawn_blocking(move || {
178 if let Ok(rid) = git.current_commit() {
179 let newer = git
180 .get_new_commits_with_oauth(&secret)
181 .ok()
182 .unwrap_or_default();
183 (
184 p,
185 id,
186 GitState::Live {
187 commit: rid.id,
188 updates: newer,
189 },
190 )
191 } else {
192 (p, id, GitState::None)
193 }
194 });
195 }
196 }
197 }
198
199 while let Some(r) = js.join_next().await {
200 match r {
201 Err(e) => return Err(e.to_string().into()),
202 Ok((p, id, s)) => r2.push((p, id, s)),
203 }
204 }
205
206 Ok(r2)
207 }
208
209 pub(super) async fn update_from_branch(
210 id: Option<NonZeroU32>,
211 archive: ArchiveId,
212 url: String,
213 branch: String,
214 ) -> Result<(usize, NonZeroU32), ServerFnError<String>> {
215 use flams_math_archives::Archive;
216 use flams_math_archives::BuildableArchive;
217 use flams_math_archives::backend::{AnyBackend, LocalBackend, SandboxedRepository};
218 use flams_math_archives::formats::FormatOrTargets;
219
220 let (_, secret) = get_oauth()?;
221 let login = LoginState::get_server();
222 if matches!(login, LoginState::NoAccounts) {
223 return Err("Only allowed in public mode".to_string().into());
224 }
225 blocking_server_fn(move || {
226 login.with_opt_queue(id, |queue_id, queue| {
227 let AnyBackend::Sandbox(backend) = queue.backend() else {
228 unreachable!()
229 };
230 backend.require(&archive);
231 let path = backend.path_for(&archive);
232 if !path.exists() {
233 return Err(format!("Archive {archive} not found!"));
234 }
235 let repo = flams_git::repos::GitRepo::open(&path).map_err(|e| e.to_string())?;
236 repo.fetch_branch_from_oauth(&secret, &branch, false)
237 .map_err(|e| e.to_string())?;
238 let commit = repo
239 .current_remote_commit_on(&branch)
240 .map_err(|e| e.to_string())?;
241 repo.force_checkout(&commit.id).map_err(|e| e.to_string())?;
242 backend.add(
244 SandboxedRepository::Git {
245 id: archive.clone(),
246 commit,
247 branch: branch.into(),
248 remote: url.into(),
249 },
250 || (),
251 );
252 let formats = backend.with_archive(&archive, |a| {
253 let Some(Archive::Local(a)) = a else {
254 return Err("Archive not found".to_string());
255 };
256 Ok(a.file_state()
257 .formats
258 .iter()
259 .map(|(k, _)| *k)
260 .collect::<Vec<_>>())
261 })?;
262 let mut u = 0;
263 for f in formats {
264 u += queue.enqueue_archive(
265 &archive,
266 FormatOrTargets::Format(f),
267 true,
268 None,
269 false,
270 );
271 }
272 Ok((u, queue_id.into()))
273 })?
274 })
275 .await
276 }
277
278 pub async fn clone_to_queue(
279 id: Option<NonZeroU32>,
280 archive: ArchiveId,
281 url: String,
282 branch: String,
283 _has_release: bool,
284 ) -> Result<(usize, NonZeroU32), ServerFnError<String>> {
285 use flams_math_archives::Archive;
286 use flams_math_archives::BuildableArchive;
287 use flams_math_archives::backend::{AnyBackend, LocalBackend, SandboxedRepository};
288 use flams_math_archives::formats::FormatOrTargets;
289
290 let (_, secret) = get_oauth()?;
291 let login = LoginState::get_server();
292 if matches!(login, LoginState::NoAccounts) {
293 return Err("Only allowed in public mode".to_string().into());
294 }
295
296 tokio::task::spawn_blocking(move || {
297 login.with_opt_queue(id, |queue_id, queue| {
298 let AnyBackend::Sandbox(backend) = queue.backend() else {
299 unreachable!()
300 };
301 let path = backend.path_for(&archive);
302 if path.exists() {
303 let _ = std::fs::remove_dir_all(&path);
304 }
305 let commit = {
306 let repo = flams_git::repos::GitRepo::clone_from_oauth(
307 &secret, &url, &branch, &path, false,
308 )
309 .map_err(|e| e.to_string())?;
310 repo.current_commit().map_err(|e| e.to_string())?
311 };
315 backend.add(
316 SandboxedRepository::Git {
317 id: archive.clone(),
318 commit,
319 branch: branch.into(),
320 remote: url.into(),
321 },
322 || (),
323 );
324 let formats = backend.with_archive(&archive, |a| {
325 let Some(Archive::Local(a)) = a else {
326 return Err("Archive not found".to_string());
327 };
328 Ok(a.file_state()
329 .formats
330 .iter()
331 .map(|(k, _)| *k)
332 .collect::<Vec<_>>())
333 })?;
334 let mut u = 0;
335 for f in formats {
336 u += queue.enqueue_archive(
337 &archive,
338 FormatOrTargets::Format(f),
339 false,
340 None,
341 false,
342 );
343 }
344 Ok((u, queue_id.into()))
345 })
346 })
347 .await
348 .unwrap_or_else(|e| Err(e.to_string()))? }
350
351 pub(super) async fn get_new_commits(
352 queue: Option<NonZeroU32>,
353 id: ArchiveId,
354 ) -> Result<Vec<(String, flams_backend_types::git::Commit)>, ServerFnError<String>> {
355 use flams_math_archives::backend::AnyBackend;
356
357 let (_, secret) = get_oauth()?;
358 let login = LoginState::get_server();
359 if matches!(login, LoginState::NoAccounts) {
360 return Err("Only allowed in public mode".to_string().into());
361 }
362 blocking_server_fn(move || {
363 login.with_opt_queue(queue, |_, queue| {
364 let AnyBackend::Sandbox(backend) = queue.backend() else {
365 impossible!()
366 };
367 let path = backend.path_for(&id);
368 let r = flams_git::repos::GitRepo::open(path)
369 .ok()
370 .and_then(|git| {
371 let gitlab_url =
372 unwrap!(flams_system::settings::Settings::get().gitlab_url.as_ref());
373 if gitlab_url
374 .host_str()
375 .is_some_and(|s| git.is_managed(s).is_some())
376 {
377 git.get_new_commits_with_oauth(&secret).ok()
378 } else {
379 None
380 }
381 })
382 .unwrap_or_default();
383 Ok(r)
384 })?
385 })
386 .await
387 }
388}