add: extended app storage limits
This commit is contained in:
parent
c757ddb77a
commit
9aed5de097
12 changed files with 143 additions and 20 deletions
|
@ -258,6 +258,7 @@ version = "1.0.0"
|
||||||
"developer:label.change_homepage" = "Change homepage"
|
"developer:label.change_homepage" = "Change homepage"
|
||||||
"developer:label.change_redirect" = "Change redirect URL"
|
"developer:label.change_redirect" = "Change redirect URL"
|
||||||
"developer:label.change_quota_status" = "Change quota status"
|
"developer:label.change_quota_status" = "Change quota status"
|
||||||
|
"developer:label.change_storage_capacity" = "Change storage capacity"
|
||||||
"developer:label.manage_scopes" = "Manage scopes"
|
"developer:label.manage_scopes" = "Manage scopes"
|
||||||
"developer:label.scopes" = "Scopes"
|
"developer:label.scopes" = "Scopes"
|
||||||
"developer:label.guides_and_help" = "Guides & help"
|
"developer:label.guides_and_help" = "Guides & help"
|
||||||
|
|
|
@ -44,6 +44,28 @@
|
||||||
("value" "Unlimited")
|
("value" "Unlimited")
|
||||||
("selected" "{% if app.quota_status == 'Unlimited' -%}true{% else %}false{%- endif %}")
|
("selected" "{% if app.quota_status == 'Unlimited' -%}true{% else %}false{%- endif %}")
|
||||||
(text "Unlimited")))))
|
(text "Unlimited")))))
|
||||||
|
(div
|
||||||
|
("class" "card-nest")
|
||||||
|
(div
|
||||||
|
("class" "card small flex items-center gap-2")
|
||||||
|
(icon (text "database-zap"))
|
||||||
|
(b (str (text "developer:label.change_storage_capacity"))))
|
||||||
|
(div
|
||||||
|
("class" "card")
|
||||||
|
(select
|
||||||
|
("onchange" "save_storage_capacity(event)")
|
||||||
|
(option
|
||||||
|
("value" "Tier1")
|
||||||
|
("selected" "{% if app.storage_capacity == 'Tier1' -%}true{% else %}false{%- endif %}")
|
||||||
|
(text "Tier 1 (25 MB)"))
|
||||||
|
(option
|
||||||
|
("value" "Tier2")
|
||||||
|
("selected" "{% if app.storage_capacity == 'Tier2' -%}true{% else %}false{%- endif %}")
|
||||||
|
(text "Tier 2 (50 MB)"))
|
||||||
|
(option
|
||||||
|
("value" "Tier3")
|
||||||
|
("selected" "{% if app.storage_capacity == 'Tier3' -%}true{% else %}false{%- endif %}")
|
||||||
|
(text "Tier 3 (100 MB)")))))
|
||||||
(text "{%- endif %}")
|
(text "{%- endif %}")
|
||||||
(div
|
(div
|
||||||
("class" "card-nest")
|
("class" "card-nest")
|
||||||
|
@ -232,6 +254,26 @@
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
globalThis.save_storage_capacity = (event) => {
|
||||||
|
const selected = event.target.selectedOptions[0];
|
||||||
|
fetch(\"/api/v1/apps/{{ app.id }}/storage_capacity\", {
|
||||||
|
method: \"POST\",
|
||||||
|
headers: {
|
||||||
|
\"Content-Type\": \"application/json\",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
storage_capacity: selected.value,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((res) => {
|
||||||
|
trigger(\"atto::toast\", [
|
||||||
|
res.ok ? \"success\" : \"error\",
|
||||||
|
res.message,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
globalThis.change_title = async (e) => {
|
globalThis.change_title = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,7 @@ pub async fn create_request(
|
||||||
|
|
||||||
// check size
|
// check size
|
||||||
let new_size = app.data_used + req.value.len();
|
let new_size = app.data_used + req.value.len();
|
||||||
if new_size > AppData::user_limit(&owner) {
|
if new_size > AppData::user_limit(&owner, &app) {
|
||||||
return Json(Error::AppHitStorageLimit.into());
|
return Json(Error::AppHitStorageLimit.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ pub async fn update_value_request(
|
||||||
let size_without = app.data_used - app_data.value.len();
|
let size_without = app.data_used - app_data.value.len();
|
||||||
let new_size = size_without + req.value.len();
|
let new_size = size_without + req.value.len();
|
||||||
|
|
||||||
if new_size > AppData::user_limit(&owner) {
|
if new_size > AppData::user_limit(&owner, &app) {
|
||||||
return Json(Error::AppHitStorageLimit.into());
|
return Json(Error::AppHitStorageLimit.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ use tetratto_core::model::{
|
||||||
ApiReturn, Error,
|
ApiReturn, Error,
|
||||||
};
|
};
|
||||||
use tetratto_shared::{hash::random_id, unix_epoch_timestamp};
|
use tetratto_shared::{hash::random_id, unix_epoch_timestamp};
|
||||||
use super::CreateApp;
|
use super::{CreateApp, UpdateAppStorageCapacity};
|
||||||
|
|
||||||
pub async fn create_request(
|
pub async fn create_request(
|
||||||
jar: CookieJar,
|
jar: CookieJar,
|
||||||
|
@ -138,6 +138,35 @@ pub async fn update_quota_status_request(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn update_storage_capacity_request(
|
||||||
|
jar: CookieJar,
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
Path(id): Path<usize>,
|
||||||
|
Json(req): Json<UpdateAppStorageCapacity>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await).0;
|
||||||
|
let user = match get_user_from_token!(jar, data) {
|
||||||
|
Some(ua) => ua,
|
||||||
|
None => return Json(Error::NotAllowed.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !user.permissions.check(FinePermission::MANAGE_APPS) {
|
||||||
|
return Json(Error::NotAllowed.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
match data
|
||||||
|
.update_app_storage_capacity(id, req.storage_capacity)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "App updated".to_string(),
|
||||||
|
payload: (),
|
||||||
|
}),
|
||||||
|
Err(e) => Json(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn update_scopes_request(
|
pub async fn update_scopes_request(
|
||||||
jar: CookieJar,
|
jar: CookieJar,
|
||||||
Extension(data): Extension<State>,
|
Extension(data): Extension<State>,
|
||||||
|
|
|
@ -20,9 +20,9 @@ use axum::{
|
||||||
routing::{any, delete, get, post, put},
|
routing::{any, delete, get, post, put},
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize};
|
use serde::Deserialize;
|
||||||
use tetratto_core::model::{
|
use tetratto_core::model::{
|
||||||
apps::{AppDataSelectMode, AppDataSelectQuery, AppQuota},
|
apps::{AppDataSelectMode, AppDataSelectQuery, AppQuota, DeveloperPassStorageQuota},
|
||||||
auth::AchievementName,
|
auth::AchievementName,
|
||||||
communities::{
|
communities::{
|
||||||
CommunityContext, CommunityJoinAccess, CommunityReadAccess, CommunityWriteAccess,
|
CommunityContext, CommunityJoinAccess, CommunityReadAccess, CommunityWriteAccess,
|
||||||
|
@ -432,6 +432,10 @@ pub fn routes() -> Router {
|
||||||
"/apps/{id}/quota_status",
|
"/apps/{id}/quota_status",
|
||||||
post(apps::update_quota_status_request),
|
post(apps::update_quota_status_request),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/apps/{id}/storage_capacity",
|
||||||
|
post(apps::update_storage_capacity_request),
|
||||||
|
)
|
||||||
.route("/apps/{id}/scopes", post(apps::update_scopes_request))
|
.route("/apps/{id}/scopes", post(apps::update_scopes_request))
|
||||||
.route("/apps/{id}/grant", post(apps::grant_request))
|
.route("/apps/{id}/grant", post(apps::grant_request))
|
||||||
.route("/apps/{id}/roll", post(apps::roll_api_key_request))
|
.route("/apps/{id}/roll", post(apps::roll_api_key_request))
|
||||||
|
@ -1031,6 +1035,11 @@ pub struct UpdateAppQuotaStatus {
|
||||||
pub quota_status: AppQuota,
|
pub quota_status: AppQuota,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct UpdateAppStorageCapacity {
|
||||||
|
pub storage_capacity: DeveloperPassStorageQuota,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct UpdateAppScopes {
|
pub struct UpdateAppScopes {
|
||||||
pub scopes: Vec<AppScope>,
|
pub scopes: Vec<AppScope>,
|
||||||
|
|
|
@ -62,7 +62,7 @@ pub async fn app_request(
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let data_limit = AppData::user_limit(&user);
|
let data_limit = AppData::user_limit(&user, &app);
|
||||||
|
|
||||||
let lang = get_lang!(jar, data.0);
|
let lang = get_lang!(jar, data.0);
|
||||||
let mut context = initial_context(&data.0.0.0, lang, &Some(user)).await;
|
let mut context = initial_context(&data.0.0.0, lang, &Some(user)).await;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use oiseau::cache::Cache;
|
use oiseau::cache::Cache;
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
apps::{AppQuota, ThirdPartyApp},
|
apps::{AppQuota, ThirdPartyApp, DeveloperPassStorageQuota},
|
||||||
auth::User,
|
auth::User,
|
||||||
oauth::AppScope,
|
oauth::AppScope,
|
||||||
permissions::{FinePermission, SecondaryPermission},
|
permissions::{FinePermission, SecondaryPermission},
|
||||||
|
@ -25,6 +25,7 @@ impl DataManager {
|
||||||
scopes: serde_json::from_str(&get!(x->9(String))).unwrap(),
|
scopes: serde_json::from_str(&get!(x->9(String))).unwrap(),
|
||||||
api_key: get!(x->10(String)),
|
api_key: get!(x->10(String)),
|
||||||
data_used: get!(x->11(i32)) as usize,
|
data_used: get!(x->11(i32)) as usize,
|
||||||
|
storage_capacity: serde_json::from_str(&get!(x->12(String))).unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +96,7 @@ impl DataManager {
|
||||||
|
|
||||||
let res = execute!(
|
let res = execute!(
|
||||||
&conn,
|
&conn,
|
||||||
"INSERT INTO apps VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)",
|
"INSERT INTO apps VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)",
|
||||||
params![
|
params![
|
||||||
&(data.id as i64),
|
&(data.id as i64),
|
||||||
&(data.created as i64),
|
&(data.created as i64),
|
||||||
|
@ -108,7 +109,8 @@ impl DataManager {
|
||||||
&(data.grants as i32),
|
&(data.grants as i32),
|
||||||
&serde_json::to_string(&data.scopes).unwrap(),
|
&serde_json::to_string(&data.scopes).unwrap(),
|
||||||
&data.api_key,
|
&data.api_key,
|
||||||
&(data.data_used as i32)
|
&(data.data_used as i32),
|
||||||
|
&serde_json::to_string(&data.storage_capacity).unwrap(),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -167,6 +169,7 @@ impl DataManager {
|
||||||
auto_method!(update_app_quota_status(AppQuota)@get_app_by_id -> "UPDATE apps SET quota_status = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_app);
|
auto_method!(update_app_quota_status(AppQuota)@get_app_by_id -> "UPDATE apps SET quota_status = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_app);
|
||||||
auto_method!(update_app_scopes(Vec<AppScope>)@get_app_by_id:FinePermission::MANAGE_APPS; -> "UPDATE apps SET scopes = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_app);
|
auto_method!(update_app_scopes(Vec<AppScope>)@get_app_by_id:FinePermission::MANAGE_APPS; -> "UPDATE apps SET scopes = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_app);
|
||||||
auto_method!(update_app_api_key(&str)@get_app_by_id -> "UPDATE apps SET api_key = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_app);
|
auto_method!(update_app_api_key(&str)@get_app_by_id -> "UPDATE apps SET api_key = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_app);
|
||||||
|
auto_method!(update_app_storage_capacity(DeveloperPassStorageQuota)@get_app_by_id -> "UPDATE apps SET storage_capacity = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_app);
|
||||||
|
|
||||||
auto_method!(update_app_data_used(i32)@get_app_by_id -> "UPDATE apps SET data_used = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_app);
|
auto_method!(update_app_data_used(i32)@get_app_by_id -> "UPDATE apps SET data_used = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_app);
|
||||||
auto_method!(add_app_data_used(i32)@get_app_by_id -> "UPDATE apps SET data_used = data_used + $1 WHERE id = $2" --cache-key-tmpl=cache_clear_app);
|
auto_method!(add_app_data_used(i32)@get_app_by_id -> "UPDATE apps SET data_used = data_used + $1 WHERE id = $2" --cache-key-tmpl=cache_clear_app);
|
||||||
|
|
|
@ -9,5 +9,6 @@ CREATE TABLE IF NOT EXISTS apps (
|
||||||
banned INT NOT NULL,
|
banned INT NOT NULL,
|
||||||
grants INT NOT NULL,
|
grants INT NOT NULL,
|
||||||
scopes TEXT NOT NULL,
|
scopes TEXT NOT NULL,
|
||||||
data_used INT NOT NULL CHECK (data_used >= 0)
|
data_used INT NOT NULL CHECK (data_used >= 0),
|
||||||
|
storage_capacity TEXT NOT NULL
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,3 +5,7 @@ ADD COLUMN IF NOT EXISTS channel_mutes TEXT DEFAULT '[]';
|
||||||
-- users is_deactivated
|
-- users is_deactivated
|
||||||
ALTER TABLE users
|
ALTER TABLE users
|
||||||
ADD COLUMN IF NOT EXISTS is_deactivated INT DEFAULT 0;
|
ADD COLUMN IF NOT EXISTS is_deactivated INT DEFAULT 0;
|
||||||
|
|
||||||
|
-- apps storage_capacity
|
||||||
|
ALTER TABLE apps
|
||||||
|
ADD COLUMN IF NOT EXISTS storage_capacity TEXT DEFAULT '"Tier1"';
|
||||||
|
|
|
@ -21,6 +21,35 @@ impl Default for AppQuota {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The storage limit for apps where the owner has a developer pass.
|
||||||
|
///
|
||||||
|
/// Free users are always limited to 500 KB.
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum DeveloperPassStorageQuota {
|
||||||
|
/// The app is limited to 25 MB.
|
||||||
|
Tier1,
|
||||||
|
/// The app is limited to 50 MB.
|
||||||
|
Tier2,
|
||||||
|
/// The app is limited to 100 MB.
|
||||||
|
Tier3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DeveloperPassStorageQuota {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Tier1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeveloperPassStorageQuota {
|
||||||
|
pub fn limit(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
DeveloperPassStorageQuota::Tier1 => 26214400,
|
||||||
|
DeveloperPassStorageQuota::Tier2 => 52428800,
|
||||||
|
DeveloperPassStorageQuota::Tier3 => 104857600,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An app is required to request grants on user accounts.
|
/// An app is required to request grants on user accounts.
|
||||||
///
|
///
|
||||||
/// Users must approve grants through a web portal.
|
/// Users must approve grants through a web portal.
|
||||||
|
@ -90,6 +119,8 @@ pub struct ThirdPartyApp {
|
||||||
pub api_key: String,
|
pub api_key: String,
|
||||||
/// The number of bytes the app's app_data rows are using.
|
/// The number of bytes the app's app_data rows are using.
|
||||||
pub data_used: usize,
|
pub data_used: usize,
|
||||||
|
/// The app's storage capacity.
|
||||||
|
pub storage_capacity: DeveloperPassStorageQuota,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThirdPartyApp {
|
impl ThirdPartyApp {
|
||||||
|
@ -102,12 +133,13 @@ impl ThirdPartyApp {
|
||||||
title,
|
title,
|
||||||
homepage,
|
homepage,
|
||||||
redirect,
|
redirect,
|
||||||
quota_status: AppQuota::Limited,
|
quota_status: AppQuota::default(),
|
||||||
banned: false,
|
banned: false,
|
||||||
grants: 0,
|
grants: 0,
|
||||||
scopes: Vec::new(),
|
scopes: Vec::new(),
|
||||||
api_key: String::new(),
|
api_key: String::new(),
|
||||||
data_used: 0,
|
data_used: 0,
|
||||||
|
storage_capacity: DeveloperPassStorageQuota::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,12 +164,16 @@ impl AppData {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the data limit of a given user.
|
/// Get the data limit of a given user.
|
||||||
pub fn user_limit(user: &User) -> usize {
|
pub fn user_limit(user: &User, app: &ThirdPartyApp) -> usize {
|
||||||
if user
|
if user
|
||||||
.secondary_permissions
|
.secondary_permissions
|
||||||
.check(SecondaryPermission::DEVELOPER_PASS)
|
.check(SecondaryPermission::DEVELOPER_PASS)
|
||||||
{
|
{
|
||||||
PASS_DATA_LIMIT
|
if app.storage_capacity != DeveloperPassStorageQuota::Tier1 {
|
||||||
|
app.storage_capacity.limit()
|
||||||
|
} else {
|
||||||
|
PASS_DATA_LIMIT
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
FREE_DATA_LIMIT
|
FREE_DATA_LIMIT
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
oauth::AuthGrant,
|
oauth::AuthGrant,
|
||||||
permissions::{FinePermission, SecondaryPermission},
|
permissions::{FinePermission, SecondaryPermission},
|
||||||
|
|
|
@ -159,12 +159,11 @@ pub fn parse_alignment(input: &str) -> String {
|
||||||
let mut buffer = String::new();
|
let mut buffer = String::new();
|
||||||
|
|
||||||
for line in lines {
|
for line in lines {
|
||||||
match line {
|
if line.starts_with("```") {
|
||||||
"```" => {
|
is_in_pre = !is_in_pre;
|
||||||
is_in_pre = !is_in_pre;
|
output.push_str(&format!("{line}\n"));
|
||||||
output.push_str(&format!("{line}\n"));
|
} else {
|
||||||
}
|
parse_alignment_line(line, &mut output, &mut buffer, is_in_pre)
|
||||||
_ => parse_alignment_line(line, &mut output, &mut buffer, is_in_pre),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue