JavaScript is disabled, refresh for a better experience. ambee/giterated

ambee/giterated

Git repository hosting, collaboration, and discovery for the Fediverse.

Add authentication operations

Amber - ⁨2⁩ years ago

parent: tbd commit: ⁨c27b4f3

⁨giterated-models/src/repository/mod.rs⁩ - ⁨9000⁩ bytes
Raw
1 use std::fmt::{Display, Formatter};
2 use std::str::FromStr;
3
4 use serde::{Deserialize, Serialize};
5
6 use crate::object::GiteratedObject;
7
8 use super::{instance::Instance, user::User};
9
10 mod operations;
11 mod settings;
12 mod values;
13
14 pub use operations::*;
15 pub use settings::*;
16 pub use values::*;
17
18 /// A repository, defined by the instance it exists on along with
19 /// its owner and name.
20 ///
21 /// # Textual Format
22 /// A repository's textual reference is defined as:
23 ///
24 /// `{owner: User}/{name: String}@{instance: Instance}`
25 ///
26 /// # Examples
27 /// For the repository named `foo` owned by `barson:giterated.dev` on the instance
28 /// `giterated.dev`, the following [`Repository`] initialization would
29 /// be valid:
30 ///
31 /// ```
32 //# use giterated_models::model::repository::Repository;
33 //# use giterated_models::model::instance::Instance;
34 //# use giterated_models::model::user::User;
35 /// let repository = Repository {
36 /// owner: User::from_str("barson:giterated.dev").unwrap(),
37 /// name: String::from("foo"),
38 /// instance: Instance::from_str("giterated.dev").unwrap()
39 /// };
40 ///
41 /// // This is correct
42 /// assert_eq!(Repository::from_str("barson:giterated.dev/[email protected]").unwrap(), repository);
43 /// ```
44 #[derive(Hash, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
45 pub struct Repository {
46 pub owner: User,
47 pub name: String,
48 /// Instance the repository is on
49 pub instance: Instance,
50 }
51
52 impl Display for Repository {
53 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
54 f.write_str(&format!("{}/{}@{}", self.owner, self.name, self.instance))
55 }
56 }
57
58 impl GiteratedObject for Repository {
59 fn object_name() -> &'static str {
60 "repository"
61 }
62
63 fn from_object_str(object_str: &str) -> Result<Self, anyhow::Error> {
64 Ok(Repository::from_str(object_str)?)
65 }
66 }
67
68 impl TryFrom<String> for Repository {
69 type Error = RepositoryParseError;
70
71 fn try_from(value: String) -> Result<Self, Self::Error> {
72 Self::from_str(&value)
73 }
74 }
75
76 impl FromStr for Repository {
77 type Err = RepositoryParseError;
78
79 fn from_str(s: &str) -> Result<Self, Self::Err> {
80 let mut by_ampersand = s.split('@');
81 let mut path_split = by_ampersand.next().ok_or(RepositoryParseError)?.split('/');
82
83 let instance = Instance::from_str(by_ampersand.next().ok_or(RepositoryParseError)?)
84 .map_err(|_| RepositoryParseError)?;
85 let owner = User::from_str(path_split.next().ok_or(RepositoryParseError)?)
86 .map_err(|_| RepositoryParseError)?;
87 let name = path_split.next().ok_or(RepositoryParseError)?.to_string();
88
89 Ok(Self {
90 instance,
91 owner,
92 name,
93 })
94 }
95 }
96
97 #[derive(Debug, thiserror::Error)]
98 #[error("no parse!")]
99 pub struct RepositoryParseError;
100
101 /// Visibility of the repository to the general eye
102 #[derive(PartialEq, Eq, Debug, Hash, Serialize, Deserialize, Clone, sqlx::Type)]
103 #[sqlx(type_name = "visibility", rename_all = "lowercase")]
104 pub enum RepositoryVisibility {
105 Public,
106 Unlisted,
107 Private,
108 }
109
110 /// Implements [`Display`] for [`RepositoryVisiblity`] using [`Debug`]
111 impl Display for RepositoryVisibility {
112 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
113 write!(f, "{:?}", self)
114 }
115 }
116
117 #[derive(Clone, Debug, Serialize, Deserialize)]
118 pub struct RepositoryView {
119 /// Name of the repository
120 ///
121 /// This is different than the [`Repository`] name,
122 /// which may be a path.
123 pub name: String,
124 /// Owner of the Repository
125 pub owner: User,
126 /// Repository description
127 pub description: Option<Description>,
128 /// Repository visibility
129 pub visibility: Visibility,
130 /// Default branch of the repository
131 pub default_branch: DefaultBranch,
132 /// Last commit made to the repository
133 pub latest_commit: Option<LatestCommit>,
134 /// Revision of the displayed tree
135 pub tree_rev: Option<String>,
136 /// Repository tree
137 pub tree: Vec<RepositoryTreeEntry>,
138 }
139
140 #[derive(Clone, Debug, Serialize, Deserialize)]
141 pub struct RepositoryFile {
142 /// Content of the file
143 pub content: Vec<u8>,
144 /// If the file is binary or not
145 pub binary: bool,
146 /// File size in bytes
147 pub size: usize,
148 }
149
150 #[derive(Clone, Debug, Serialize, Deserialize)]
151 pub struct RepositoryDiff {
152 /// "to" side of the diff commit
153 pub new_commit: Commit,
154 /// Total number of files changed
155 pub files_changed: usize,
156 /// Total number of insertions
157 pub insertions: usize,
158 /// Total number of deletions
159 pub deletions: usize,
160 /// Preferably unified patch, probably a git patch.
161 pub patch: String,
162 }
163
164 #[derive(Debug, Clone, Serialize, Deserialize)]
165 pub enum RepositoryObjectType {
166 Tree,
167 Blob,
168 }
169
170 /// Stored info for our tree entries
171 #[derive(Debug, Clone, Serialize, Deserialize)]
172 pub struct RepositoryTreeEntry {
173 /// ID of the tree/blob
174 pub id: String,
175 /// Name of the tree/blob
176 pub name: String,
177 /// Type of the tree entry
178 pub object_type: RepositoryObjectType,
179 /// Git supplies us with the mode at all times, and people like it displayed.
180 pub mode: i32,
181 /// File size
182 pub size: Option<usize>,
183 /// Last commit made to the tree/blob
184 pub last_commit: Option<Commit>,
185 }
186
187 impl RepositoryTreeEntry {
188 // I love you Emilia <3
189 pub fn new(id: &str, name: &str, object_type: RepositoryObjectType, mode: i32) -> Self {
190 Self {
191 id: id.to_string(),
192 name: name.to_string(),
193 object_type,
194 mode,
195 size: None,
196 last_commit: None,
197 }
198 }
199 }
200
201 #[derive(Debug, Clone, Serialize, Deserialize)]
202 pub struct RepositoryTreeEntryWithCommit {
203 pub tree_entry: RepositoryTreeEntry,
204 pub commit: Commit,
205 }
206
207 /// Info about a git commit
208 #[derive(PartialEq, Hash, Eq, Debug, Clone, Serialize, Deserialize)]
209 pub struct Commit {
210 /// Unique commit ID
211 pub oid: String,
212 /// Shortened abbreviated OID
213 /// This starts at the git config's "core.abbrev" length (default 7 characters) and
214 /// iteratively extends to a longer string if that length is ambiguous. The
215 /// result will be unambiguous (at least until new objects are added to the repository).
216 pub short_oid: String,
217 /// First paragraph of the full message
218 pub summary: Option<String>,
219 /// Everything in the full message apart from the first paragraph
220 pub body: Option<String>,
221 /// All commit id's of the parents of this commit
222 pub parents: Vec<String>,
223 /// Who created the commit
224 pub author: CommitSignature,
225 /// Who committed the commit
226 pub committer: CommitSignature,
227 /// Time when the commit happened
228 pub time: chrono::NaiveDateTime,
229 }
230
231 /// Gets all info from [`git2::Commit`] for easy use
232 impl From<git2::Commit<'_>> for Commit {
233 fn from(commit: git2::Commit<'_>) -> Self {
234 Self {
235 oid: commit.id().to_string(),
236 // This shouldn't ever fail, as we already know the object has an oid.
237 short_oid: commit
238 .as_object()
239 .short_id()
240 .unwrap()
241 .as_str()
242 .unwrap()
243 .to_string(),
244 summary: commit.summary().map(|summary| summary.to_string()),
245 body: commit.body().map(|body| body.to_string()),
246 parents: commit
247 .parents()
248 .map(|parent| parent.id().to_string())
249 .collect::<Vec<String>>(),
250 author: commit.author().into(),
251 committer: commit.committer().into(),
252 time: chrono::NaiveDateTime::from_timestamp_opt(commit.time().seconds(), 0).unwrap(),
253 }
254 }
255 }
256
257 /// Git commit signature
258 #[derive(PartialEq, Hash, Eq, Debug, Clone, Serialize, Deserialize)]
259 pub struct CommitSignature {
260 pub name: Option<String>,
261 pub email: Option<String>,
262 pub time: chrono::NaiveDateTime,
263 }
264
265 /// Converts the signature from git2 into something usable without explicit lifetimes.
266 impl From<git2::Signature<'_>> for CommitSignature {
267 fn from(signature: git2::Signature<'_>) -> Self {
268 Self {
269 name: signature.name().map(|name| name.to_string()),
270 email: signature.email().map(|email| email.to_string()),
271 time: chrono::NaiveDateTime::from_timestamp_opt(signature.when().seconds(), 0).unwrap(),
272 }
273 }
274 }
275
276 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
277 pub struct RepositorySummary {
278 pub repository: Repository,
279 pub owner: User,
280 pub visibility: RepositoryVisibility,
281 pub description: Option<String>,
282 pub last_commit: Option<Commit>,
283 }
284
285 #[derive(Clone, Debug, Serialize, Deserialize)]
286 pub struct IssueLabel {
287 pub name: String,
288 pub color: String,
289 }
290
291 #[derive(Clone, Debug, Serialize, Deserialize)]
292 pub struct RepositoryIssue {
293 pub author: User,
294 pub id: u64,
295 pub title: String,
296 pub contents: String,
297 pub labels: Vec<IssueLabel>,
298 }
299