feat(nextcloud): add command 'nextcloud board list'
REQUEST
This commit is contained in:
parent
0f4da9c48d
commit
63f99c68c4
7 changed files with 237 additions and 163 deletions
|
@ -4,7 +4,7 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
#bytes = "1.9"
|
bytes = "1.9"
|
||||||
clap = { version = "4.5", features = ["cargo", "derive"] }
|
clap = { version = "4.5", features = ["cargo", "derive"] }
|
||||||
color-eyre = "0.6"
|
color-eyre = "0.6"
|
||||||
derive_more = { version = "1.0", features = [
|
derive_more = { version = "1.0", features = [
|
||||||
|
|
21
src/lib.rs
21
src/lib.rs
|
@ -34,6 +34,22 @@ enum Command {
|
||||||
Init,
|
Init,
|
||||||
Check,
|
Check,
|
||||||
Import,
|
Import,
|
||||||
|
#[clap(subcommand)]
|
||||||
|
Nextcloud(NextcloudCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
enum NextcloudCommand {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
Board(NextcloudBoardCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
enum NextcloudBoardCommand {
|
||||||
|
List {
|
||||||
|
#[clap(long, action = clap::ArgAction::SetTrue)]
|
||||||
|
dump: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -80,7 +96,7 @@ pub async fn run(ctx: Ctx) -> color_eyre::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(cfg) => {
|
Ok(cfg) => {
|
||||||
let _ctx = FullCtx {
|
let ctx = FullCtx {
|
||||||
fs: ctx.fs,
|
fs: ctx.fs,
|
||||||
net: ctx.net,
|
net: ctx.net,
|
||||||
prt: ctx.prt,
|
prt: ctx.prt,
|
||||||
|
@ -90,6 +106,9 @@ pub async fn run(ctx: Ctx) -> color_eyre::Result<()> {
|
||||||
Command::Init => Err(eyre!("Config file already exists. Not overwriting it.")),
|
Command::Init => Err(eyre!("Config file already exists. Not overwriting it.")),
|
||||||
Command::Check => todo!("check"),
|
Command::Check => todo!("check"),
|
||||||
Command::Import => todo!("import"),
|
Command::Import => todo!("import"),
|
||||||
|
Command::Nextcloud(NextcloudCommand::Board(NextcloudBoardCommand::List {
|
||||||
|
dump,
|
||||||
|
})) => nextcloud::board::list(ctx, dump).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,16 @@
|
||||||
//
|
//
|
||||||
use kxio::net::Net;
|
|
||||||
|
|
||||||
use crate::{p, AppConfig, Ctx};
|
|
||||||
use crate::{p, FullCtx};
|
use crate::{p, FullCtx};
|
||||||
|
|
||||||
use super::DeckClient;
|
|
||||||
|
|
||||||
pub async fn list(ctx: FullCtx, dump: bool) -> color_eyre::Result<()> {
|
pub async fn list(ctx: FullCtx, dump: bool) -> color_eyre::Result<()> {
|
||||||
let dc = DeckClient::new(&ctx.cfg.nextcloud, ctx.net);
|
let api_result = ctx.deck_client().get_boards().await;
|
||||||
let apiresult = dc.get_boards().await;
|
if dump {
|
||||||
if dump {
|
p!(ctx.prt, "{}", api_result.text);
|
||||||
p!("{}", apiresult.text);
|
} else {
|
||||||
} else {
|
let mut boards = api_result.result?;
|
||||||
let mut boards = apiresult.result?;
|
boards.sort_by_key(|stack| stack.title.clone());
|
||||||
boards.sort_by_key(|stack| stack.title.clone());
|
boards
|
||||||
boards
|
.iter()
|
||||||
.iter()
|
.for_each(|stack| p!(ctx.prt, "{}:{}", stack.id, stack.title));
|
||||||
.for_each(|stack| p!("{}:{}", stack.id, stack.title));
|
}
|
||||||
}
|
Ok(())
|
||||||
Ok(())
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
//
|
//
|
||||||
|
use bytes::Bytes;
|
||||||
|
use kxio::net::{Net, ReqBuilder};
|
||||||
|
|
||||||
use crate::{api_result::APIResult, f, FullCtx};
|
use crate::{api_result::APIResult, f, FullCtx};
|
||||||
|
|
||||||
use crate::nextcloud::model::{NextcloudHostname, NextcloudPassword, NextcloudUsername};
|
use crate::nextcloud::model::{NextcloudHostname, NextcloudPassword, NextcloudUsername};
|
||||||
use model::{Board, Card, NextcloudBoardId, Stack};
|
use model::{Board, Card, NextcloudBoardId, Stack};
|
||||||
|
|
||||||
|
pub mod board;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -34,13 +38,16 @@ impl<'ctx> DeckClient<'ctx> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_boards(&self) -> APIResult<Vec<Board>> {
|
async fn request<T: for<'a> serde::Deserialize<'a>>(
|
||||||
|
&self,
|
||||||
|
url: impl Into<String>,
|
||||||
|
custom: fn(&Net, String) -> ReqBuilder,
|
||||||
|
) -> APIResult<T> {
|
||||||
APIResult::new(
|
APIResult::new(
|
||||||
self.ctx
|
custom(&self.ctx.net, self.url(url))
|
||||||
.net
|
|
||||||
.get(self.url("boards"))
|
|
||||||
.basic_auth(self.username.as_str(), Some(self.password.as_str()))
|
.basic_auth(self.username.as_str(), Some(self.password.as_str()))
|
||||||
.header("accept", "application/json")
|
.header("accept", "application/json")
|
||||||
|
.header("content-type", "application/json")
|
||||||
.send()
|
.send()
|
||||||
.await,
|
.await,
|
||||||
&self.ctx.prt,
|
&self.ctx.prt,
|
||||||
|
@ -48,53 +55,50 @@ impl<'ctx> DeckClient<'ctx> {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn request_with_body<T: for<'a> serde::Deserialize<'a>>(
|
||||||
|
&self,
|
||||||
|
url: impl Into<String>,
|
||||||
|
body: impl Into<Bytes>,
|
||||||
|
custom: fn(&Net, String) -> ReqBuilder,
|
||||||
|
) -> APIResult<T> {
|
||||||
|
APIResult::new(
|
||||||
|
custom(&self.ctx.net, self.url(url))
|
||||||
|
.basic_auth(self.username.as_str(), Some(self.password.as_str()))
|
||||||
|
.header("accept", "application/json")
|
||||||
|
.header("content-type", "application/json")
|
||||||
|
.body(body)
|
||||||
|
.send()
|
||||||
|
.await,
|
||||||
|
&self.ctx.prt,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_boards(&self) -> APIResult<Vec<Board>> {
|
||||||
|
self.request("boards", |net, url| net.get(url)).await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_board(&self, board_id: NextcloudBoardId) -> APIResult<Board> {
|
pub async fn get_board(&self, board_id: NextcloudBoardId) -> APIResult<Board> {
|
||||||
APIResult::new(
|
self.request(f!("boards/{board_id}"), |net, url| net.get(url))
|
||||||
self.ctx
|
.await
|
||||||
.net
|
|
||||||
.get(self.url(f!("boards/{board_id}")))
|
|
||||||
.basic_auth(self.username.as_str(), Some(self.password.as_str()))
|
|
||||||
.header("accept", "application/json")
|
|
||||||
.send()
|
|
||||||
.await,
|
|
||||||
&self.ctx.prt,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_board(&self, title: &str, color: &str) -> APIResult<Board> {
|
pub async fn create_board(&self, title: &str, color: &str) -> APIResult<Board> {
|
||||||
APIResult::new(
|
self.request_with_body(
|
||||||
self.ctx
|
"boards",
|
||||||
.net
|
serde_json::json!({
|
||||||
.post(self.url("boards"))
|
"title": title,
|
||||||
.basic_auth(self.username.as_str(), Some(self.password.as_str()))
|
"color": color
|
||||||
.header("accept", "application/json")
|
})
|
||||||
.body(
|
.to_string(),
|
||||||
serde_json::json!({
|
|net, url| net.post(url),
|
||||||
"title": title,
|
|
||||||
"color": color
|
|
||||||
})
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
.send()
|
|
||||||
.await,
|
|
||||||
&self.ctx.prt,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_stacks(&self, board_id: NextcloudBoardId) -> APIResult<Vec<Stack>> {
|
pub async fn get_stacks(&self, board_id: NextcloudBoardId) -> APIResult<Vec<Stack>> {
|
||||||
APIResult::new(
|
self.request(f!("boards/{board_id}/stacks"), |net, url| net.get(url))
|
||||||
self.ctx
|
.await
|
||||||
.net
|
|
||||||
.get(self.url(f!("boards/{board_id}/stacks")))
|
|
||||||
.basic_auth(self.username.as_str(), Some(self.password.as_str()))
|
|
||||||
.header("accept", "application/json")
|
|
||||||
.send()
|
|
||||||
.await,
|
|
||||||
&self.ctx.prt,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_card(
|
pub async fn create_card(
|
||||||
|
@ -104,29 +108,18 @@ impl<'ctx> DeckClient<'ctx> {
|
||||||
title: &str,
|
title: &str,
|
||||||
description: Option<&str>,
|
description: Option<&str>,
|
||||||
) -> APIResult<Card> {
|
) -> APIResult<Card> {
|
||||||
let url = format!(
|
let mut body = serde_json::json!({
|
||||||
"https://{}/index.php/apps/deck/api/v1.0/boards/{}/stacks/{}/cards",
|
|
||||||
self.hostname, board_id, stack_id
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut json = serde_json::json!({
|
|
||||||
"title": title,
|
"title": title,
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(desc) = description {
|
if let Some(desc) = description {
|
||||||
json["description"] = serde_json::Value::String(desc.to_string());
|
body["description"] = serde_json::Value::String(desc.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
APIResult::new(
|
self.request_with_body(
|
||||||
self.ctx
|
format!("boards/{}/stacks/{}/cards", board_id, stack_id),
|
||||||
.net
|
body.to_string(),
|
||||||
.post(&url)
|
|net, url| net.post(url),
|
||||||
.basic_auth(self.username.as_str(), Some(self.password.as_str()))
|
|
||||||
.header("accept", "application/json")
|
|
||||||
.body(json.to_string())
|
|
||||||
.send()
|
|
||||||
.await,
|
|
||||||
&self.ctx.prt,
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use derive_more::derive::Display;
|
|
||||||
//
|
//
|
||||||
|
use derive_more::derive::Display;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::newtype;
|
use crate::newtype;
|
||||||
|
@ -83,17 +83,11 @@ newtype!(
|
||||||
newtype!(
|
newtype!(
|
||||||
NextcloudBoardTitle,
|
NextcloudBoardTitle,
|
||||||
String,
|
String,
|
||||||
|
Display,
|
||||||
PartialOrd,
|
PartialOrd,
|
||||||
Ord,
|
Ord,
|
||||||
"Title of the Board"
|
"Title of the Board"
|
||||||
);
|
);
|
||||||
newtype!(
|
|
||||||
NextcloudBoardOwner,
|
|
||||||
String,
|
|
||||||
PartialOrd,
|
|
||||||
Ord,
|
|
||||||
"Owner of the Board"
|
|
||||||
);
|
|
||||||
newtype!(
|
newtype!(
|
||||||
NextcloudBoardColour,
|
NextcloudBoardColour,
|
||||||
String,
|
String,
|
||||||
|
@ -101,7 +95,6 @@ newtype!(
|
||||||
Ord,
|
Ord,
|
||||||
"Colour of the Board"
|
"Colour of the Board"
|
||||||
);
|
);
|
||||||
|
|
||||||
newtype!(
|
newtype!(
|
||||||
NextcloudStackTitle,
|
NextcloudStackTitle,
|
||||||
String,
|
String,
|
||||||
|
@ -117,6 +110,15 @@ newtype!(
|
||||||
"Title of the Card"
|
"Title of the Card"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub struct NextcloudBoardOwner {
|
||||||
|
#[serde(rename = "primaryKey")]
|
||||||
|
pub primary_key: String,
|
||||||
|
pub uid: String,
|
||||||
|
#[serde(rename = "displayname")]
|
||||||
|
pub display_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Board {
|
pub struct Board {
|
||||||
pub id: NextcloudBoardId,
|
pub id: NextcloudBoardId,
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
//
|
//
|
||||||
|
use kxio::net::StatusCode;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::NextcloudConfig,
|
config::NextcloudConfig,
|
||||||
nextcloud::{
|
nextcloud::{
|
||||||
model::{NextcloudBoardId, NextcloudHostname, NextcloudPassword, NextcloudUsername},
|
model::{
|
||||||
|
Board, NextcloudBoardColour, NextcloudBoardId, NextcloudBoardOwner,
|
||||||
|
NextcloudBoardTitle, NextcloudETag, NextcloudHostname, NextcloudOrder,
|
||||||
|
NextcloudPassword, NextcloudStackId, NextcloudStackTitle, NextcloudUsername, Stack,
|
||||||
|
},
|
||||||
DeckClient,
|
DeckClient,
|
||||||
},
|
},
|
||||||
|
s, AppConfig, FullCtx,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod config {
|
mod config {
|
||||||
|
@ -95,86 +102,59 @@ mod config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod client {
|
mod commands {
|
||||||
use kxio::net::StatusCode;
|
|
||||||
use serde_json::json;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
nextcloud::model::{
|
|
||||||
Acl, Board, Label, NextcloudBoardColour, NextcloudBoardOwner, NextcloudBoardTitle,
|
|
||||||
NextcloudETag, NextcloudLabelId, NextcloudOrder, NextcloudStackId, NextcloudStackTitle,
|
|
||||||
Stack,
|
|
||||||
},
|
|
||||||
s,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[tokio::test]
|
mod board {
|
||||||
async fn get_boards() {
|
|
||||||
//given
|
|
||||||
let mock_net = kxio::net::mock();
|
|
||||||
|
|
||||||
mock_net
|
use super::*;
|
||||||
.on()
|
|
||||||
.get("https://host-name/index.php/apps/deck/api/v1.0/boards")
|
|
||||||
.basic_auth("username", Some("password"))
|
|
||||||
.respond(StatusCode::OK)
|
|
||||||
.body(
|
|
||||||
json!([{
|
|
||||||
"id":2,
|
|
||||||
"title":"board-title",
|
|
||||||
"owner":"owner",
|
|
||||||
"color":"red",
|
|
||||||
"archived":false,
|
|
||||||
"labels":[
|
|
||||||
{
|
|
||||||
"id":2,
|
|
||||||
"title":"label-title",
|
|
||||||
"color":"blue"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"acl":[
|
|
||||||
{
|
|
||||||
"participant":"participant",
|
|
||||||
"permission_edit":true,
|
|
||||||
"permission_share":false,
|
|
||||||
"permission_manage":true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}])
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
.expect("mock request");
|
|
||||||
|
|
||||||
let fs = given::a_filesystem();
|
#[tokio::test]
|
||||||
let ctx = given::a_full_context(mock_net, fs);
|
async fn list() {
|
||||||
let deck_client = DeckClient::new(&ctx);
|
//given
|
||||||
|
let mock_net = kxio::net::mock();
|
||||||
|
|
||||||
//when
|
mock_net
|
||||||
let result = deck_client.get_boards().await.result.expect("get boards");
|
.on()
|
||||||
|
.get("https://host-name/index.php/apps/deck/api/v1.0/boards")
|
||||||
|
.basic_auth("username", Some("password"))
|
||||||
|
.respond(StatusCode::OK)
|
||||||
|
.body(include_str!("../tests/responses/nextcloud-board-list.json"))
|
||||||
|
.expect("mock request");
|
||||||
|
|
||||||
assert_eq!(
|
let fs = given::a_filesystem();
|
||||||
result,
|
let ctx = FullCtx {
|
||||||
vec![Board {
|
fs: fs.as_real(),
|
||||||
id: NextcloudBoardId::new(2),
|
net: mock_net.into(),
|
||||||
title: NextcloudBoardTitle::new("board-title"),
|
prt: given::a_printer(),
|
||||||
owner: NextcloudBoardOwner::new("owner"),
|
cfg: AppConfig {
|
||||||
color: NextcloudBoardColour::new("red"),
|
trello: given::a_trello_config(),
|
||||||
archived: false,
|
nextcloud: given::a_nextcloud_config(),
|
||||||
labels: vec![Label {
|
},
|
||||||
id: NextcloudLabelId::new(2),
|
};
|
||||||
title: s!("label-title"),
|
let deck_client = DeckClient::new(&ctx);
|
||||||
color: s!("blue")
|
|
||||||
}],
|
//when
|
||||||
acl: vec![Acl {
|
let result = deck_client.get_boards().await.result.expect("get boards");
|
||||||
participant: s!("participant"),
|
|
||||||
permission_edit: true,
|
assert_eq!(
|
||||||
permission_share: false,
|
result.first(),
|
||||||
permission_manage: true
|
Some(&Board {
|
||||||
}]
|
id: NextcloudBoardId::new(1),
|
||||||
}]
|
title: NextcloudBoardTitle::new("Personal Board"),
|
||||||
);
|
owner: NextcloudBoardOwner {
|
||||||
|
primary_key: s!("pcampbell"),
|
||||||
|
uid: s!("pcampbell"),
|
||||||
|
display_name: s!("Paul Campbell"),
|
||||||
|
},
|
||||||
|
color: NextcloudBoardColour::new("0087C5"),
|
||||||
|
archived: false,
|
||||||
|
labels: vec![],
|
||||||
|
acl: vec![]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
86
src/tests/responses/nextcloud-board-list.json
Normal file
86
src/tests/responses/nextcloud-board-list.json
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Personal Board",
|
||||||
|
"owner": {
|
||||||
|
"primaryKey": "pcampbell",
|
||||||
|
"uid": "pcampbell",
|
||||||
|
"displayname": "Paul Campbell",
|
||||||
|
"type": 0
|
||||||
|
},
|
||||||
|
"color": "0087C5",
|
||||||
|
"archived": false,
|
||||||
|
"labels": [],
|
||||||
|
"acl": [],
|
||||||
|
"permissions": {
|
||||||
|
"PERMISSION_READ": true,
|
||||||
|
"PERMISSION_EDIT": true,
|
||||||
|
"PERMISSION_MANAGE": true,
|
||||||
|
"PERMISSION_SHARE": true
|
||||||
|
},
|
||||||
|
"users": [],
|
||||||
|
"shared": 0,
|
||||||
|
"stacks": [],
|
||||||
|
"activeSessions": [],
|
||||||
|
"deletedAt": 0,
|
||||||
|
"lastModified": 1733337423,
|
||||||
|
"settings": [],
|
||||||
|
"ETag": "b567d287210fa4d9b108ac68d5b087c1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"title": "4 Published: Cossmass Infinities",
|
||||||
|
"owner": {
|
||||||
|
"primaryKey": "pcampbell",
|
||||||
|
"uid": "pcampbell",
|
||||||
|
"displayname": "Paul Campbell",
|
||||||
|
"type": 0
|
||||||
|
},
|
||||||
|
"color": "ff0000",
|
||||||
|
"archived": true,
|
||||||
|
"labels": [],
|
||||||
|
"acl": [],
|
||||||
|
"permissions": {
|
||||||
|
"PERMISSION_READ": true,
|
||||||
|
"PERMISSION_EDIT": true,
|
||||||
|
"PERMISSION_MANAGE": true,
|
||||||
|
"PEMISSION_SHARE": true
|
||||||
|
},
|
||||||
|
"users": [],
|
||||||
|
"shared": 0,
|
||||||
|
"stacks": [],
|
||||||
|
"activeSessions": [],
|
||||||
|
"deletedAt": 0,
|
||||||
|
"lastModified": 1699798570,
|
||||||
|
"settings": [],
|
||||||
|
"ETag": "5e0fe035f3b95672da3cba633086be37"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"title": "Fulfilment: Cossmass Infinities",
|
||||||
|
"owner": {
|
||||||
|
"primaryKey": "pcampbell",
|
||||||
|
"uid": "pcampbell",
|
||||||
|
"displayname": "Paul Campbell",
|
||||||
|
"type": 0
|
||||||
|
},
|
||||||
|
"color": "ff0000",
|
||||||
|
"archived": true,
|
||||||
|
"labels": [],
|
||||||
|
"acl": [],
|
||||||
|
"permissions": {
|
||||||
|
"PERMISSION_READ": true,
|
||||||
|
"PERMISSION_EDIT": true,
|
||||||
|
"PERMISSION_MANAGE": true,
|
||||||
|
"PERMISSION_SHARE": true
|
||||||
|
},
|
||||||
|
"users": [],
|
||||||
|
"shared": 0,
|
||||||
|
"stacks": [],
|
||||||
|
"activeSessions": [],
|
||||||
|
"deletedAt": 0,
|
||||||
|
"lastModified": 1699798567,
|
||||||
|
"settings": [],
|
||||||
|
"ETag": "90e2f9d53c5f6ec83088425d4486e54d"
|
||||||
|
}
|
||||||
|
]
|
Loading…
Reference in a new issue