Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Project settings say who created the project, by name | 1 | //! GitHub: signing in with a GitHub account, and bringing repositories |
| 2 | //! across through g1t's GitHub App. Mirrors `packages/contracts/src/github.ts`. | |
| 3 | //! | |
| 4 | //! One GitHub App does both. Its user authorization (OAuth web flow, with | |
| 5 | //! PKCE) signs people in and lets g1t list what they installed it on; its | |
| 6 | //! installations give g1t access to the repositories they chose. The app | |
| 7 | //! is optional: with none configured every method here says so, and the | |
| 8 | //! site shows no GitHub buttons. | |
| 9 | //! | |
| 10 | //! The identity service (`POST /rpc/<method>`) keeps linked accounts and | |
| 11 | //! their user tokens: | |
| 12 | //! | |
| 13 | //! - `github_enabled` (no arguments) returns `bool`. | |
| 14 | //! - `github_start` takes `GithubStartArgs`, returns `Outcome<GithubStart>`. | |
| 15 | //! - `github_finish` takes `GithubFinishArgs`, returns `Outcome<GithubFinished>`. | |
| 16 | //! - `github_pending` takes `GithubPendingArgs`, returns `Outcome<GithubPending>`. | |
| 17 | //! - `github_sign_up` takes `GithubSignUpArgs`, returns `Outcome<SignedIn>`. | |
| 18 | //! - `github_claim` takes `GithubClaimArgs`, returns `Outcome<GithubAccount>`. | |
| 19 | //! - `github_account` takes `UserArgs`, returns `GithubAccountView`. | |
| 20 | //! - `github_unlink` takes `UserArgs`, returns `Outcome<bool>`. | |
| 21 | //! - `github_user_token` takes `GithubUserTokenArgs`, returns `Outcome<String>`. | |
| 22 | //! - `github_revoked` takes `GithubRevokedArgs`, returns `u32`. | |
| 23 | //! - `github_usernames` takes `GithubUsernamesArgs`, returns a map. | |
| 24 | //! | |
| Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them. | 25 | //! The integrations service keeps installations and the repositories |
| 26 | //! imported through them, and receives the app's webhook at `https://api.g1t.sh/hooks/github`: | |
| Project settings say who created the project, by name | 27 | //! |
| 28 | //! - `github_status` takes `GithubStatusArgs`, returns `Outcome<GithubAppStatus>`. | |
| 29 | //! - `github_add_installation` takes `GithubInstallationArgs`, returns | |
| 30 | //! `Outcome<GithubInstallation>`. | |
| 31 | //! - `github_remove_installation` takes `GithubInstallationArgs`, returns | |
| 32 | //! `Outcome<bool>`. | |
| A GitHub App installed straight on GitHub can be added to a workspace: Import from GitHub lists the installations your GitHub account can see that this workspace hasn't added, each with Add for owners, and coming back from GitHub without g1t's own start asks which of your workspaces to add it to instead of stopping; the GitHub guide says how. | 33 | //! - `github_visible_installations` takes `GithubVisibleArgs`, returns |
| 34 | //! `Outcome<Vec<GithubVisibleInstallation>>`. | |
| Project settings say who created the project, by name | 35 | //! - `github_repositories` takes `GithubRepositoriesArgs`, returns |
| 36 | //! `Outcome<GithubRepositories>`. | |
| 37 | //! - `github_import` takes `GithubImportArgs`, returns `Outcome<GithubRepoLink>`. | |
| 38 | //! - `github_link` takes `GithubLinkArgs`, returns `Option<GithubRepoLink>`. | |
| 39 | //! - `github_receive` takes `GithubReceiveArgs`, returns `integrations::Received`. | |
| 40 | ||
| 41 | use std::collections::HashMap; | |
| 42 | ||
| 43 | use serde::{Deserialize, Serialize}; | |
| 44 | ||
| 45 | use crate::User; | |
| 46 | use crate::identity::SignedIn; | |
| 47 | ||
| 48 | /// Why someone is sent to GitHub. | |
| 49 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] | |
| 50 | #[serde(rename_all = "snake_case")] | |
| 51 | pub enum GithubPurpose { | |
| 52 | /// Sign in, or create an account. | |
| 53 | SignIn, | |
| 54 | /// Link GitHub to the signed-in account. | |
| 55 | Link, | |
| 56 | } | |
| 57 | ||
| 58 | impl GithubPurpose { | |
| 59 | pub fn as_str(self) -> &'static str { | |
| 60 | match self { | |
| 61 | GithubPurpose::SignIn => "sign_in", | |
| 62 | GithubPurpose::Link => "link", | |
| 63 | } | |
| 64 | } | |
| 65 | } | |
| 66 | ||
| 67 | #[derive(Debug, Serialize, Deserialize)] | |
| 68 | #[serde(rename_all = "camelCase")] | |
| 69 | pub struct GithubStartArgs { | |
| 70 | pub purpose: GithubPurpose, | |
| 71 | /// The signed-in person, for `link`. | |
| 72 | #[serde(default)] | |
| 73 | pub user: Option<User>, | |
| 74 | /// Exactly the callback registered on the app, such as | |
| 75 | /// `https://g1t.sh/auth/github/callback`. | |
| 76 | pub redirect_uri: String, | |
| 77 | /// A same-site path to return to afterwards. | |
| 78 | #[serde(default)] | |
| 79 | pub next: String, | |
| 80 | /// An invite code, from `/register?invite=…`, carried through GitHub | |
| Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them. | 81 | /// so a new account made with it gets the invitation it came with. |
| Project settings say who created the project, by name | 82 | #[serde(default)] |
| 83 | pub invite_code: Option<String>, | |
| 84 | } | |
| 85 | ||
| 86 | /// Where to send the browser, and the state to bind to it in a cookie. | |
| 87 | #[derive(Debug, Serialize, Deserialize)] | |
| 88 | #[serde(rename_all = "camelCase")] | |
| 89 | pub struct GithubStart { | |
| 90 | pub authorize_url: String, | |
| 91 | pub state: String, | |
| 92 | } | |
| 93 | ||
| 94 | #[derive(Debug, Serialize, Deserialize)] | |
| 95 | pub struct GithubFinishArgs { | |
| 96 | pub state: String, | |
| 97 | pub code: String, | |
| 98 | } | |
| 99 | ||
| 100 | /// How a return from GitHub ended. | |
| 101 | #[derive(Debug, Serialize, Deserialize)] | |
| 102 | #[serde(tag = "kind", rename_all = "snake_case")] | |
| 103 | pub enum GithubFinished { | |
| 104 | /// Signed in to the account the GitHub account is linked to, or to a | |
| 105 | /// new one made with it. | |
| 106 | SignedIn { | |
| 107 | #[serde(rename = "signedIn")] | |
| 108 | signed_in: SignedIn, | |
| 109 | created: bool, | |
| 110 | next: String, | |
| 111 | }, | |
| 112 | /// Linked to the account that asked. | |
| 113 | Linked { login: String, next: String }, | |
| 114 | /// An account with one of its verified emails exists: the person signs | |
| 115 | /// in to it, and `github_claim` then links it. | |
| 116 | NeedsLink { pending: String, login: String, next: String }, | |
| 117 | /// A new account, but the GitHub login cannot be its username. | |
| 118 | NeedsUsername { | |
| 119 | pending: String, | |
| 120 | login: String, | |
| 121 | suggestion: String, | |
| 122 | next: String, | |
| 123 | }, | |
| 124 | } | |
| 125 | ||
| 126 | #[derive(Debug, Serialize, Deserialize)] | |
| 127 | pub struct GithubPendingArgs { | |
| 128 | pub pending: String, | |
| 129 | } | |
| 130 | ||
| 131 | /// A GitHub sign-in waiting on a username or on signing in to link. | |
| 132 | #[derive(Debug, Serialize, Deserialize)] | |
| 133 | pub struct GithubPending { | |
| 134 | pub login: String, | |
| 135 | /// `link` or `username`. | |
| 136 | pub kind: String, | |
| 137 | pub suggestion: Option<String>, | |
| 138 | pub next: String, | |
| 139 | } | |
| 140 | ||
| 141 | #[derive(Debug, Serialize, Deserialize)] | |
| 142 | pub struct GithubSignUpArgs { | |
| 143 | pub pending: String, | |
| 144 | pub username: String, | |
| 145 | } | |
| 146 | ||
| 147 | #[derive(Debug, Serialize, Deserialize)] | |
| 148 | pub struct GithubClaimArgs { | |
| 149 | pub pending: String, | |
| 150 | pub user: User, | |
| 151 | } | |
| 152 | ||
| 153 | /// A linked GitHub account. The numeric id is what identifies it; the | |
| 154 | /// login is for showing, and is refreshed at each sign-in. | |
| 155 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 156 | #[serde(rename_all = "camelCase")] | |
| 157 | pub struct GithubAccount { | |
| 158 | pub github_id: u64, | |
| 159 | pub login: String, | |
| 160 | /// RFC 3339. | |
| 161 | pub linked_at: String, | |
| 162 | /// Whether g1t holds a working user token, needed to list installations. | |
| 163 | pub authorized: bool, | |
| 164 | } | |
| 165 | ||
| 166 | #[derive(Debug, Default, Serialize, Deserialize)] | |
| 167 | #[serde(rename_all = "camelCase")] | |
| 168 | pub struct GithubAccountView { | |
| 169 | /// Whether this g1t has a GitHub App configured for sign-in. | |
| 170 | pub enabled: bool, | |
| 171 | pub account: Option<GithubAccount>, | |
| 172 | /// Whether the account has a password, so GitHub is not its only way in. | |
| 173 | pub has_password: bool, | |
| 174 | } | |
| 175 | ||
| 176 | #[derive(Debug, Serialize, Deserialize)] | |
| 177 | #[serde(rename_all = "camelCase")] | |
| 178 | pub struct GithubUserTokenArgs { | |
| 179 | pub user_id: String, | |
| 180 | } | |
| 181 | ||
| 182 | #[derive(Debug, Serialize, Deserialize)] | |
| 183 | #[serde(rename_all = "camelCase")] | |
| 184 | pub struct GithubRevokedArgs { | |
| 185 | pub github_id: u64, | |
| 186 | } | |
| 187 | ||
| 188 | /// `github_usernames`: the g1t usernames of linked GitHub accounts. Returns | |
| 189 | /// a map from GitHub id, as a string, to username. | |
| 190 | #[derive(Debug, Serialize, Deserialize)] | |
| 191 | #[serde(rename_all = "camelCase")] | |
| 192 | pub struct GithubUsernamesArgs { | |
| 193 | pub github_ids: Vec<u64>, | |
| 194 | } | |
| 195 | ||
| 196 | // --- The app's installations and repositories (integrations) ------------- | |
| 197 | ||
| 198 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 199 | #[serde(rename_all = "camelCase")] | |
| 200 | pub struct GithubInstallation { | |
| 201 | pub id: u64, | |
| 202 | pub workspace: String, | |
| 203 | /// The GitHub user or organization it is installed on. | |
| 204 | pub account: String, | |
| 205 | /// `User` or `Organization`. | |
| 206 | pub account_type: String, | |
| 207 | /// `all` or `selected` repositories. | |
| 208 | pub repository_selection: String, | |
| 209 | pub suspended: bool, | |
| 210 | /// Where to change which repositories it can see. | |
| 211 | pub settings_url: String, | |
| 212 | pub created_at: String, | |
| 213 | } | |
| 214 | ||
| 215 | #[derive(Debug, Serialize, Deserialize)] | |
| 216 | #[serde(rename_all = "camelCase")] | |
| 217 | pub struct GithubStatusArgs { | |
| 218 | pub viewer: User, | |
| 219 | pub workspace: String, | |
| 220 | } | |
| 221 | ||
| 222 | #[derive(Debug, Default, Serialize, Deserialize)] | |
| 223 | #[serde(rename_all = "camelCase")] | |
| 224 | pub struct GithubAppStatus { | |
| 225 | /// Whether this g1t has a GitHub App configured for repositories. | |
| 226 | pub configured: bool, | |
| 227 | /// `https://github.com/apps/<slug>/installations/new`. | |
| 228 | pub install_url: Option<String>, | |
| 229 | /// Whether the viewer has linked GitHub with a working user token. | |
| 230 | pub linked: bool, | |
| 231 | pub installations: Vec<GithubInstallation>, | |
| 232 | } | |
| 233 | ||
| 234 | #[derive(Debug, Serialize, Deserialize)] | |
| 235 | #[serde(rename_all = "camelCase")] | |
| 236 | pub struct GithubInstallationArgs { | |
| 237 | pub actor: User, | |
| 238 | pub workspace: String, | |
| 239 | pub installation_id: u64, | |
| 240 | } | |
| 241 | ||
| A GitHub App installed straight on GitHub can be added to a workspace: Import from GitHub lists the installations your GitHub account can see that this workspace hasn't added, each with Add for owners, and coming back from GitHub without g1t's own start asks which of your workspaces to add it to instead of stopping; the GitHub guide says how. | 242 | /// `github_visible_installations`: the app's installations the person's |
| 243 | /// own GitHub account can see, for claiming one that was installed on | |
| 244 | /// GitHub directly rather than from g1t. | |
| Project settings say who created the project, by name | 245 | #[derive(Debug, Serialize, Deserialize)] |
| 246 | #[serde(rename_all = "camelCase")] | |
| A GitHub App installed straight on GitHub can be added to a workspace: Import from GitHub lists the installations your GitHub account can see that this workspace hasn't added, each with Add for owners, and coming back from GitHub without g1t's own start asks which of your workspaces to add it to instead of stopping; the GitHub guide says how. | 247 | pub struct GithubVisibleArgs { |
| 248 | pub actor: User, | |
| 249 | } | |
| 250 | ||
| 251 | /// An installation of the app, as the person's GitHub account sees it. | |
| 252 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] | |
| 253 | #[serde(rename_all = "camelCase")] | |
| 254 | pub struct GithubVisibleInstallation { | |
| 255 | pub id: u64, | |
| 256 | /// The GitHub user or organization it is installed on. | |
| 257 | pub account: String, | |
| 258 | /// `User` or `Organization`. | |
| 259 | pub account_type: String, | |
| 260 | /// `all` or `selected` repositories. | |
| 261 | pub repository_selection: String, | |
| 262 | pub suspended: bool, | |
| 263 | pub settings_url: String, | |
| 264 | /// The person's workspaces it is recorded in already. | |
| 265 | pub recorded_in: Vec<String>, | |
| 266 | } | |
| 267 | ||
| 268 | #[derive(Debug, Serialize, Deserialize)] | |
| 269 | #[serde(rename_all = "camelCase")] | |
| Project settings say who created the project, by name | 270 | pub struct GithubRepositoriesArgs { |
| 271 | pub actor: User, | |
| 272 | pub workspace: String, | |
| 273 | pub installation_id: u64, | |
| 274 | #[serde(default)] | |
| 275 | pub page: Option<u32>, | |
| 276 | } | |
| 277 | ||
| 278 | #[derive(Clone, Debug, Serialize, Deserialize)] | |
| 279 | #[serde(rename_all = "camelCase")] | |
| 280 | pub struct GithubRepository { | |
| 281 | pub id: u64, | |
| 282 | /// `owner/name`. | |
| 283 | pub full_name: String, | |
| 284 | pub name: String, | |
| 285 | pub private: bool, | |
| 286 | pub description: Option<String>, | |
| 287 | pub default_branch: String, | |
| Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them. | 288 | /// The g1t repository already imported from it, as `workspace/name`. |
| Project settings say who created the project, by name | 289 | pub linked_to: Option<String>, |
| 290 | } | |
| 291 | ||
| 292 | #[derive(Debug, Default, Serialize, Deserialize)] | |
| 293 | #[serde(rename_all = "camelCase")] | |
| 294 | pub struct GithubRepositories { | |
| 295 | pub repositories: Vec<GithubRepository>, | |
| 296 | pub total: u32, | |
| 297 | pub page: u32, | |
| 298 | pub per_page: u32, | |
| 299 | } | |
| 300 | ||
| 301 | #[derive(Debug, Serialize, Deserialize)] | |
| 302 | #[serde(rename_all = "camelCase")] | |
| 303 | pub struct GithubImportArgs { | |
| 304 | pub actor: User, | |
| 305 | pub workspace: String, | |
| 306 | pub installation_id: u64, | |
| 307 | pub github_repo_id: u64, | |
| 308 | /// The g1t repository's name; the GitHub one's when left out. | |
| 309 | #[serde(default)] | |
| 310 | pub name: Option<String>, | |
| 311 | /// Private on g1t; as on GitHub when left out. | |
| 312 | #[serde(default)] | |
| 313 | pub private: Option<bool>, | |
| 314 | /// Also copy issues, with their labels, milestone and state. | |
| 315 | #[serde(default)] | |
| 316 | pub issues: bool, | |
| 317 | } | |
| 318 | ||
| Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them. | 319 | /// Where a g1t repository was imported from on GitHub. The import is |
| 320 | /// copied once: the g1t repository is then its own. | |
| Project settings say who created the project, by name | 321 | #[derive(Clone, Debug, Serialize, Deserialize)] |
| 322 | #[serde(rename_all = "camelCase")] | |
| 323 | pub struct GithubRepoLink { | |
| 324 | pub repo_id: String, | |
| 325 | /// `workspace/name` on g1t. | |
| 326 | pub repo: String, | |
| 327 | pub installation_id: u64, | |
| 328 | pub github_repo_id: u64, | |
| 329 | /// `owner/name` on GitHub. | |
| 330 | pub full_name: String, | |
| Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them. | 331 | /// RFC 3339; when it was imported. |
| Project settings say who created the project, by name | 332 | pub synced_at: Option<String>, |
| 333 | pub last_error: Option<String>, | |
| 334 | /// Issues copied so far, when they were asked for. | |
| 335 | pub issues_imported: u32, | |
| 336 | } | |
| 337 | ||
| 338 | #[derive(Debug, Serialize, Deserialize)] | |
| 339 | #[serde(rename_all = "camelCase")] | |
| 340 | pub struct GithubLinkArgs { | |
| 341 | pub repo_id: String, | |
| 342 | } | |
| 343 | ||
| 344 | #[derive(Debug, Serialize, Deserialize)] | |
| 345 | pub struct GithubReceiveArgs { | |
| 346 | /// Header names in lowercase. | |
| 347 | pub headers: HashMap<String, String>, | |
| 348 | pub body: String, | |
| 349 | } | |
| A GitHub App installed straight on GitHub can be added to a workspace: Import from GitHub lists the installations your GitHub account can see that this workspace hasn't added, each with Add for owners, and coming back from GitHub without g1t's own start asks which of your workspaces to add it to instead of stopping; the GitHub guide says how. | 350 | |
| 351 | #[cfg(test)] | |
| 352 | mod tests { | |
| 353 | use super::*; | |
| 354 | ||
| 355 | #[test] | |
| 356 | fn a_visible_installation_reads_as_the_site_expects() { | |
| 357 | let item = GithubVisibleInstallation { | |
| 358 | id: 7, | |
| 359 | account: "flagon-io".into(), | |
| 360 | account_type: "Organization".into(), | |
| 361 | repository_selection: "all".into(), | |
| 362 | suspended: false, | |
| 363 | settings_url: "https://github.com/organizations/flagon-io/settings/installations/7".into(), | |
| 364 | recorded_in: vec!["flagon-io".into()], | |
| 365 | }; | |
| 366 | let json = serde_json::to_value(&item).unwrap(); | |
| 367 | assert_eq!(json["accountType"], "Organization"); | |
| 368 | assert_eq!(json["repositorySelection"], "all"); | |
| 369 | assert_eq!(json["recordedIn"][0], "flagon-io"); | |
| 370 | assert_eq!(serde_json::from_value::<GithubVisibleInstallation>(json).unwrap(), item); | |
| 371 | } | |
| 372 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.