flams_router_git_base/
server_fns.rs

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            //let r2 = &mut ret;
138            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                                        //return Some(GitState::Queued { commit:commit.id.clone(), queue:(*qid).into()})
156                                    }
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                //repo.mark_managed(&branch,&commit.id).map_err(|e| e.to_string())?;
243                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                    //repo.new_branch("release").map_err(|e| e.to_string())?;
312                    //repo.mark_managed(&branch,&commit.id).map_err(|e| e.to_string())?;
313                    //commit
314                };
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()))? //.map_err(Into::into)
349    }
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}