flams_router_git_base/
server_fns.rs

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