···
633
+
// ========== Issues ==========
634
+
pub async fn list_issues(
637
+
repo_at_uri: Option<&str>,
638
+
bearer: Option<&str>,
639
+
) -> Result<Vec<IssueRecord>> {
640
+
#[derive(Deserialize)]
645
+
#[derive(Deserialize)]
648
+
records: Vec<Item>,
651
+
("repo", author_did.to_string()),
652
+
("collection", "sh.tangled.repo.issue".to_string()),
653
+
("limit", "100".to_string()),
655
+
let res: ListRes = self
656
+
.get_json("com.atproto.repo.listRecords", ¶ms, bearer)
658
+
let mut out = vec![];
659
+
for it in res.records {
660
+
if let Some(filter_repo) = repo_at_uri {
661
+
if it.value.repo.as_str() != filter_repo {
665
+
let rkey = Self::uri_rkey(&it.uri).unwrap_or_default();
666
+
out.push(IssueRecord {
667
+
author_did: author_did.to_string(),
675
+
#[allow(clippy::too_many_arguments)]
676
+
pub async fn create_issue(
682
+
body: Option<&str>,
685
+
) -> Result<String> {
686
+
#[derive(Serialize)]
690
+
#[serde(skip_serializing_if = "Option::is_none")]
691
+
body: Option<&'a str>,
692
+
#[serde(rename = "createdAt")]
693
+
created_at: String,
695
+
#[derive(Serialize)]
698
+
collection: &'a str,
702
+
#[derive(Deserialize)]
706
+
let issue_repo_at = format!("at://{}/sh.tangled.repo/{}", repo_did, repo_rkey);
707
+
let now = chrono::Utc::now().to_rfc3339();
709
+
repo: &issue_repo_at,
716
+
collection: "sh.tangled.repo.issue",
720
+
let pds_client = TangledClient::new(pds_base);
721
+
let res: Res = pds_client
722
+
.post_json("com.atproto.repo.createRecord", &req, Some(access_jwt))
724
+
Self::uri_rkey(&res.uri).ok_or_else(|| anyhow!("missing rkey in issue uri"))
727
+
pub async fn comment_issue(
734
+
) -> Result<String> {
735
+
#[derive(Serialize)]
739
+
#[serde(rename = "createdAt")]
740
+
created_at: String,
742
+
#[derive(Serialize)]
745
+
collection: &'a str,
749
+
#[derive(Deserialize)]
753
+
let now = chrono::Utc::now().to_rfc3339();
761
+
collection: "sh.tangled.repo.issue.comment",
765
+
let pds_client = TangledClient::new(pds_base);
766
+
let res: Res = pds_client
767
+
.post_json("com.atproto.repo.createRecord", &req, Some(access_jwt))
769
+
Self::uri_rkey(&res.uri).ok_or_else(|| anyhow!("missing rkey in issue comment uri"))
772
+
pub async fn get_issue_record(
776
+
bearer: Option<&str>,
777
+
) -> Result<Issue> {
778
+
#[derive(Deserialize)]
783
+
("repo", author_did.to_string()),
784
+
("collection", "sh.tangled.repo.issue".to_string()),
785
+
("rkey", rkey.to_string()),
787
+
let res: GetRes = self
788
+
.get_json("com.atproto.repo.getRecord", ¶ms, bearer)
793
+
pub async fn put_issue_record(
798
+
bearer: Option<&str>,
800
+
#[derive(Serialize)]
801
+
struct PutReq<'a> {
803
+
collection: &'a str,
810
+
collection: "sh.tangled.repo.issue",
815
+
let _: serde_json::Value = self
816
+
.post_json("com.atproto.repo.putRecord", &req, bearer)
821
+
pub async fn set_issue_state(
828
+
) -> Result<String> {
829
+
#[derive(Serialize)]
834
+
#[derive(Serialize)]
837
+
collection: &'a str,
841
+
#[derive(Deserialize)]
851
+
collection: "sh.tangled.repo.issue.state",
855
+
let pds_client = TangledClient::new(pds_base);
856
+
let res: Res = pds_client
857
+
.post_json("com.atproto.repo.createRecord", &req, Some(access_jwt))
859
+
Self::uri_rkey(&res.uri).ok_or_else(|| anyhow!("missing rkey in issue state uri"))
862
+
pub async fn get_pull_record(
866
+
bearer: Option<&str>,
867
+
) -> Result<Pull> {
868
+
#[derive(Deserialize)]
873
+
("repo", author_did.to_string()),
874
+
("collection", "sh.tangled.repo.pull".to_string()),
875
+
("rkey", rkey.to_string()),
877
+
let res: GetRes = self
878
+
.get_json("com.atproto.repo.getRecord", ¶ms, bearer)
883
+
// ========== Pull Requests ==========
884
+
pub async fn list_pulls(
887
+
target_repo_at_uri: Option<&str>,
888
+
bearer: Option<&str>,
889
+
) -> Result<Vec<PullRecord>> {
890
+
#[derive(Deserialize)]
895
+
#[derive(Deserialize)]
898
+
records: Vec<Item>,
901
+
("repo", author_did.to_string()),
902
+
("collection", "sh.tangled.repo.pull".to_string()),
903
+
("limit", "100".to_string()),
905
+
let res: ListRes = self
906
+
.get_json("com.atproto.repo.listRecords", ¶ms, bearer)
908
+
let mut out = vec![];
909
+
for it in res.records {
910
+
if let Some(target) = target_repo_at_uri {
911
+
if it.value.target.repo.as_str() != target {
915
+
let rkey = Self::uri_rkey(&it.uri).unwrap_or_default();
916
+
out.push(PullRecord {
917
+
author_did: author_did.to_string(),
925
+
#[allow(clippy::too_many_arguments)]
926
+
pub async fn create_pull(
931
+
target_branch: &str,
934
+
body: Option<&str>,
937
+
) -> Result<String> {
938
+
#[derive(Serialize)]
939
+
struct Target<'a> {
943
+
#[derive(Serialize)]
945
+
target: Target<'a>,
947
+
#[serde(skip_serializing_if = "Option::is_none")]
948
+
body: Option<&'a str>,
950
+
#[serde(rename = "createdAt")]
951
+
created_at: String,
953
+
#[derive(Serialize)]
956
+
collection: &'a str,
960
+
#[derive(Deserialize)]
964
+
let repo_at = format!("at://{}/sh.tangled.repo/{}", repo_did, repo_rkey);
965
+
let now = chrono::Utc::now().to_rfc3339();
969
+
branch: target_branch,
978
+
collection: "sh.tangled.repo.pull",
982
+
let pds_client = TangledClient::new(pds_base);
983
+
let res: Res = pds_client
984
+
.post_json("com.atproto.repo.createRecord", &req, Some(access_jwt))
986
+
Self::uri_rkey(&res.uri).ok_or_else(|| anyhow!("missing rkey in pull uri"))
989
+
// ========== Spindle: Secrets Management ==========
990
+
pub async fn list_repo_secrets(
995
+
) -> Result<Vec<Secret>> {
996
+
let sa = self.service_auth_token(pds_base, access_jwt).await?;
997
+
#[derive(Deserialize)]
999
+
secrets: Vec<Secret>,
1001
+
let params = [("repo", repo_at.to_string())];
1002
+
let res: Res = self
1003
+
.get_json("sh.tangled.repo.listSecrets", ¶ms, Some(&sa))
1008
+
pub async fn add_repo_secret(
1016
+
let sa = self.service_auth_token(pds_base, access_jwt).await?;
1017
+
#[derive(Serialize)]
1028
+
let _: serde_json::Value = self
1029
+
.post_json("sh.tangled.repo.addSecret", &body, Some(&sa))
1034
+
pub async fn remove_repo_secret(
1041
+
let sa = self.service_auth_token(pds_base, access_jwt).await?;
1042
+
#[derive(Serialize)]
1047
+
let body = Req { repo: repo_at, key };
1048
+
let _: serde_json::Value = self
1049
+
.post_json("sh.tangled.repo.removeSecret", &body, Some(&sa))
1054
+
async fn service_auth_token(&self, pds_base: &str, access_jwt: &str) -> Result<String> {
1057
+
.trim_end_matches('/')
1058
+
.strip_prefix("https://")
1059
+
.or_else(|| self.base_url.trim_end_matches('/').strip_prefix("http://"))
1060
+
.ok_or_else(|| anyhow!("invalid base_url"))?;
1061
+
let audience = format!("did:web:{}", host);
1062
+
#[derive(Deserialize)]
1066
+
let pds = TangledClient::new(pds_base);
1068
+
("aud", audience),
1069
+
("exp", (chrono::Utc::now().timestamp() + 600).to_string()),
1071
+
let sa: GetSARes = pds
1073
+
"com.atproto.server.getServiceAuth",
1081
+
pub async fn comment_pull(
1088
+
) -> Result<String> {
1089
+
#[derive(Serialize)]
1093
+
#[serde(rename = "createdAt")]
1094
+
created_at: String,
1096
+
#[derive(Serialize)]
1099
+
collection: &'a str,
1103
+
#[derive(Deserialize)]
1107
+
let now = chrono::Utc::now().to_rfc3339();
1115
+
collection: "sh.tangled.repo.pull.comment",
1119
+
let pds_client = TangledClient::new(pds_base);
1120
+
let res: Res = pds_client
1121
+
.post_json("com.atproto.repo.createRecord", &req, Some(access_jwt))
1123
+
Self::uri_rkey(&res.uri).ok_or_else(|| anyhow!("missing rkey in pull comment uri"))
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
···
1138
+
// Issue record value
1139
+
#[derive(Debug, Clone, Serialize, Deserialize)]
1140
+
pub struct Issue {
1142
+
pub title: String,
1145
+
#[serde(rename = "createdAt")]
1146
+
pub created_at: String,
1149
+
#[derive(Debug, Clone)]
1150
+
pub struct IssueRecord {
1151
+
pub author_did: String,
1156
+
// Pull record value (subset)
1157
+
#[derive(Debug, Clone, Serialize, Deserialize)]
1158
+
pub struct PullTarget {
1160
+
pub branch: String,
1163
+
#[derive(Debug, Clone, Serialize, Deserialize)]
1165
+
pub target: PullTarget,
1166
+
pub title: String,
1169
+
pub patch: String,
1170
+
#[serde(rename = "createdAt")]
1171
+
pub created_at: String,
1174
+
#[derive(Debug, Clone)]
1175
+
pub struct PullRecord {
1176
+
pub author_did: String,
···
#[serde(rename = "createdAt")]
1224
+
#[derive(Debug, Clone, Serialize, Deserialize)]
1225
+
pub struct Secret {
1228
+
#[serde(rename = "createdAt")]
1229
+
pub created_at: String,
1230
+
#[serde(rename = "createdBy")]
1231
+
pub created_by: String,