GitRoot
craft your forge, build your project, grow your community freely
1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
2//
3// SPDX-License-Identifier: MIT
4
5use crate::fs::Fs;
6use crate::imports;
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use std::clone::Clone;
10use std::sync::{Mutex, OnceLock};
11
12#[derive(Serialize)]
13#[serde(rename_all = "camelCase")]
14pub struct ConfPlugin {
15 pub default_run: Vec<PluginRun>,
16 pub sdk_version: String,
17 pub sdk_type: String,
18}
19
20#[derive(Serialize, Deserialize)]
21#[serde(rename_all = "camelCase")]
22pub struct Exec {
23 pub cmd: String,
24 pub args: Vec<String>,
25 pub env: Vec<String>,
26}
27
28#[derive(Debug, Deserialize, Serialize)]
29#[serde(rename_all = "camelCase")]
30pub struct ForgeConf {
31 domain: String,
32 external_ssh_addr: String
33 external_http_addr: String
34}
35
36#[derive(Debug, Deserialize)]
37#[serde(rename_all = "camelCase")]
38pub struct Commit {
39 pub branch: String,
40 pub hash: String,
41 pub message: String,
42 pub date: String, //TODO should a DateTime
43 pub committer: String,
44 pub parent_hash: String,
45}
46
47#[derive(Debug, Deserialize, Serialize, Clone)]
48#[serde(rename_all = "lowercase")]
49pub enum PluginRunWhen {
50 ADD, // = "add",
51 MOD, // = "mod",
52 DEL, // = "del",
53}
54
55#[derive(Debug, Deserialize, Serialize, Clone)]
56#[serde(rename_all = "lowercase")]
57pub enum PluginWriteRightCan {
58 ADD, // = "add",
59 MOD, // = "mod",
60 DEL, // = "del",
61 APPEND, // = "append",
62}
63
64impl PluginWriteRightCan {
65 pub fn all() -> Vec<PluginWriteRightCan> {
66 vec![
67 PluginWriteRightCan::ADD,
68 PluginWriteRightCan::MOD,
69 PluginWriteRightCan::DEL,
70 PluginWriteRightCan::APPEND,
71 ]
72 }
73}
74
75#[derive(Debug, Serialize, Clone)]
76#[serde(rename_all = "camelCase")]
77pub struct PluginRun {
78 pub path: String,
79 pub branch: Vec<String>,
80 pub when: Vec<PluginRunWhen>,
81 pub write: PluginWrite,
82 pub configuration: Value,
83}
84
85#[derive(Debug, Serialize, Clone)]
86#[serde(rename_all = "camelCase")]
87pub struct PluginWrite {
88 pub git: Vec<PluginWriteRight>,
89 pub web: Vec<PluginWriteRight>,
90 pub exec: Vec<PluginExecRight>,
91}
92
93#[derive(Debug, Serialize, Clone)]
94#[serde(rename_all = "camelCase")]
95pub struct PluginWriteRight {
96 pub path: String,
97 pub can: Vec<PluginWriteRightCan>,
98}
99
100#[derive(Debug, Serialize, Clone)]
101#[serde(rename_all = "camelCase")]
102pub struct PluginExecRight {
103 pub command: String,
104}
105
106pub trait Plugin: Send + 'static {
107 fn init(
108 &self,
109 repo_name: String,
110 conf_has_changed: bool,
111 serialized_conf: String,
112 ) -> Result<(), String>;
113 fn start_commit(&self, commit: Commit) -> Result<(), String>;
114 fn add_file(&self, path: String) -> Result<(), String>;
115 fn mod_file(&self, from_path: String, to_path: String) -> Result<(), String>;
116 fn del_file(&self, path: String) -> Result<(), String>;
117 fn end_commit(&self, commit: Commit) -> Result<(), String>;
118 fn finish(&self) -> Result<(), String>;
119}
120
121pub struct Server {
122 at_least_one_change: bool,
123 plugin: Mutex<Option<Box<dyn Plugin>>>,
124 pub run: Vec<PluginRun>,
125}
126
127static INSTANCE: OnceLock<Server> = OnceLock::new();
128
129impl Server {
130 pub fn init<T, F>(run: Vec<PluginRun>, plugin_factory: F) -> &'static Self
131 where
132 T: Plugin,
133 F: FnOnce(&'static Self) -> T,
134 {
135 let server = Server {
136 at_least_one_change: false,
137 plugin: Mutex::new(None),
138 run: run,
139 };
140 let _ = INSTANCE.set(server);
141 let static_server = INSTANCE.get().unwrap();
142 let plugin = plugin_factory(static_server);
143 *static_server.plugin.lock().unwrap() = Some(Box::new(plugin));
144 static_server
145 }
146
147 pub fn get() -> &'static Self {
148 INSTANCE
149 .get()
150 .expect("Server has not been initialized. Call Server::init() first.")
151 }
152
153 pub fn with_plugin<F>(f: F)
154 where
155 F: FnOnce(&dyn Plugin) -> Result<(), String>,
156 {
157 let guard = Self::get().plugin.lock().unwrap();
158 if let Some(plugin) = guard.as_ref() {
159 let _ = f(&*plugin.as_ref())
160 .map_err(|err| imports::log_error(String::from("unknown error"), err));
161 } else {
162 imports::log_error(
163 String::from("Plugin not initialized after server was created"),
164 String::from("Fatal error"),
165 );
166 }
167 }
168
169 pub fn forge_conf(&self) -> Result<ForgeConf, String> {
170 imports::forge_conf()
171 }
172
173 pub fn worktree(&self) -> Fs {
174 Fs::new(String::from("/worktree/"))
175 }
176
177 pub fn webcontent(&self) -> Fs {
178 Fs::new(String::from("/webcontent/"))
179 }
180
181 pub fn cache(&self) -> Fs {
182 Fs::new(String::from("/cache"))
183 }
184
185 pub fn modify_content(&mut self, filepath: String, content: String) {
186 self.at_least_one_change = true;
187 imports::modify_content(filepath, content);
188 }
189
190 pub fn modify_web_content(&self, filepath: String, content: String) {
191 imports::modify_web_content(filepath, content);
192 }
193
194 pub fn replace_web_content(&self, filepath: String, old_content: String, content: String) {
195 imports::replace_web_content(filepath, old_content, content);
196 }
197
198 pub fn modify_cache_content(&self, filepath: String, content: String) {
199 imports::modify_cache_content(filepath, content);
200 }
201
202 pub fn commit_all_if_needed(&self, message: String) {
203 if self.at_least_one_change {
204 imports::commit_all(message);
205 }
206 }
207
208 pub fn diff_with_parent(
209 &self,
210 hash: String,
211 old_filepath: String,
212 new_filepath: String,
213 ) -> Result<String, String> {
214 imports::diff_with_parent(hash, old_filepath, new_filepath)
215 }
216
217 pub fn log(&self, message: String) {
218 imports::log(message);
219 }
220
221 pub fn log_error(&self, message: String, err: String) {
222 imports::log_error(message, err);
223 }
224
225 pub fn merge(&self, from: String, to: String) {
226 imports::merge(from, to);
227 }
228
229 pub fn commits(&self, from: String, to: String) -> Result<Vec<Commit>, String> {
230 imports::commits(from, to)
231 }
232
233 pub fn exec(&self, cmd: &[Exec]) -> Result<String, String> {
234 imports::exec(cmd)
235 }
236}