Compare commits
No commits in common. "c94915e35120850a0de59ab125a929fd0c066945" and "7758385b512b85d0042ffef9720a8d12eaeeeb55" have entirely different histories.
c94915e351
...
7758385b51
18 changed files with 57 additions and 187 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1 @@
|
||||||
/target
|
/target
|
||||||
*.snap.new
|
|
||||||
*.pending-snap
|
|
||||||
|
|
94
src/api.rs
94
src/api.rs
|
@ -63,8 +63,6 @@ pub enum ApiError {
|
||||||
InvalidArchiveType,
|
InvalidArchiveType,
|
||||||
#[error("invalid color")]
|
#[error("invalid color")]
|
||||||
InvalidColor,
|
InvalidColor,
|
||||||
#[error("join error: {0}")]
|
|
||||||
TokioJoin(#[from] tokio::task::JoinError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseError for ApiError {
|
impl ResponseError for ApiError {
|
||||||
|
@ -75,7 +73,6 @@ impl ResponseError for ApiError {
|
||||||
| ApiError::InvalidArchiveType
|
| ApiError::InvalidArchiveType
|
||||||
| ApiError::InvalidColor => StatusCode::BAD_REQUEST,
|
| ApiError::InvalidColor => StatusCode::BAD_REQUEST,
|
||||||
ApiError::NoAccess => StatusCode::FORBIDDEN,
|
ApiError::NoAccess => StatusCode::FORBIDDEN,
|
||||||
ApiError::TokioJoin(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,17 +99,11 @@ impl TalonApi {
|
||||||
&self,
|
&self,
|
||||||
talon: Data<&Talon>,
|
talon: Data<&Talon>,
|
||||||
subdomain: Path<String>,
|
subdomain: Path<String>,
|
||||||
) -> Result<Response<Json<Website>>> {
|
) -> Result<Json<Website>> {
|
||||||
talon
|
talon
|
||||||
.db
|
.db
|
||||||
.get_website(&subdomain)
|
.get_website(&subdomain)
|
||||||
.map(|website| {
|
.map(|w| Json(Website::from((subdomain.0, w))))
|
||||||
let modified = website.updated_at();
|
|
||||||
Response::new(Json(Website::from((subdomain.0, website)))).header(
|
|
||||||
header::LAST_MODIFIED,
|
|
||||||
httpdate::fmt_http_date(modified.into()),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,14 +163,9 @@ impl TalonApi {
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
auth.check_subdomain(&subdomain, Access::Modify)?;
|
auth.check_subdomain(&subdomain, Access::Modify)?;
|
||||||
|
|
||||||
let t2 = talon.clone();
|
talon
|
||||||
let sd = subdomain.clone();
|
.icons
|
||||||
tokio::task::spawn_blocking(move || {
|
.insert_icon(Cursor::new(data.as_slice()), &subdomain)?;
|
||||||
t2.icons.insert_icon(Cursor::new(data.as_slice()), &sd)
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.map_err(ApiError::from)??;
|
|
||||||
|
|
||||||
talon.db.update_website(
|
talon.db.update_website(
|
||||||
&subdomain,
|
&subdomain,
|
||||||
db::model::WebsiteUpdate {
|
db::model::WebsiteUpdate {
|
||||||
|
@ -266,8 +252,7 @@ impl TalonApi {
|
||||||
/// Mimimum visibility of the websites
|
/// Mimimum visibility of the websites
|
||||||
#[oai(default)]
|
#[oai(default)]
|
||||||
visibility: Query<Visibility>,
|
visibility: Query<Visibility>,
|
||||||
) -> Result<Response<Json<Vec<Website>>>> {
|
) -> Result<Json<Vec<Website>>> {
|
||||||
let modified = talon.db.websites_last_update()?;
|
|
||||||
talon
|
talon
|
||||||
.db
|
.db
|
||||||
.get_websites()
|
.get_websites()
|
||||||
|
@ -285,10 +270,7 @@ impl TalonApi {
|
||||||
Err(_) => true,
|
Err(_) => true,
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()
|
.collect::<Result<Vec<_>, _>>()
|
||||||
.map(|data| {
|
.map(Json)
|
||||||
Response::new(Json(data))
|
|
||||||
.header(header::LAST_MODIFIED, httpdate::fmt_http_date(modified))
|
|
||||||
})
|
|
||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,19 +280,14 @@ impl TalonApi {
|
||||||
&self,
|
&self,
|
||||||
talon: Data<&Talon>,
|
talon: Data<&Talon>,
|
||||||
subdomain: Path<String>,
|
subdomain: Path<String>,
|
||||||
) -> Result<Response<Json<Vec<Version>>>> {
|
) -> Result<Json<Vec<Version>>> {
|
||||||
let website = talon.db.get_website(&subdomain)?;
|
talon.db.website_exists(&subdomain)?;
|
||||||
talon
|
talon
|
||||||
.db
|
.db
|
||||||
.get_website_versions(&subdomain)
|
.get_website_versions(&subdomain)
|
||||||
.map(|r| r.map(Version::from))
|
.map(|r| r.map(Version::from))
|
||||||
.collect::<Result<Vec<_>, _>>()
|
.collect::<Result<Vec<_>, _>>()
|
||||||
.map(|data| {
|
.map(Json)
|
||||||
Response::new(Json(data)).header(
|
|
||||||
header::LAST_MODIFIED,
|
|
||||||
httpdate::fmt_http_date(website.updated_at().into()),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,17 +298,11 @@ impl TalonApi {
|
||||||
talon: Data<&Talon>,
|
talon: Data<&Talon>,
|
||||||
subdomain: Path<String>,
|
subdomain: Path<String>,
|
||||||
version: Path<u32>,
|
version: Path<u32>,
|
||||||
) -> Result<Response<Json<Version>>> {
|
) -> Result<Json<Version>> {
|
||||||
talon
|
talon
|
||||||
.db
|
.db
|
||||||
.get_version(&subdomain, *version)
|
.get_version(&subdomain, *version)
|
||||||
.map(|v| {
|
.map(|v| Json(Version::from((*version, v))))
|
||||||
let create_date = v.created_at;
|
|
||||||
Response::new(Json(Version::from((*version, v)))).header(
|
|
||||||
header::LAST_MODIFIED,
|
|
||||||
httpdate::fmt_http_date(create_date.into()),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,19 +313,14 @@ impl TalonApi {
|
||||||
talon: Data<&Talon>,
|
talon: Data<&Talon>,
|
||||||
subdomain: Path<String>,
|
subdomain: Path<String>,
|
||||||
version: Path<u32>,
|
version: Path<u32>,
|
||||||
) -> Result<Response<Json<Vec<VersionFile>>>> {
|
) -> Result<Json<Vec<VersionFile>>> {
|
||||||
let v = talon.db.get_version(&subdomain, *version)?;
|
talon.db.version_exists(&subdomain, *version)?;
|
||||||
talon
|
talon
|
||||||
.db
|
.db
|
||||||
.get_version_files(&subdomain, *version)
|
.get_version_files(&subdomain, *version)
|
||||||
.map(|r| r.map(VersionFile::from))
|
.map(|r| r.map(VersionFile::from))
|
||||||
.collect::<Result<Vec<_>, _>>()
|
.collect::<Result<Vec<_>, _>>()
|
||||||
.map(|r| {
|
.map(Json)
|
||||||
Response::new(Json(r)).header(
|
|
||||||
header::LAST_MODIFIED,
|
|
||||||
httpdate::fmt_http_date(v.created_at.into()),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.map_err(Error::from)
|
.map_err(Error::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,21 +380,15 @@ impl TalonApi {
|
||||||
|
|
||||||
// Try to store the uploaded website
|
// Try to store the uploaded website
|
||||||
// If this fails, the new version needs to be deleted
|
// If this fails, the new version needs to be deleted
|
||||||
fn try_insert(
|
let try_insert = || {
|
||||||
talon: &Talon,
|
|
||||||
data: Binary<Vec<u8>>,
|
|
||||||
subdomain: &str,
|
|
||||||
version: u32,
|
|
||||||
fallback: Option<String>,
|
|
||||||
) -> Result<()> {
|
|
||||||
if data.starts_with(&hex!("1f8b")) {
|
if data.starts_with(&hex!("1f8b")) {
|
||||||
talon
|
talon
|
||||||
.storage
|
.storage
|
||||||
.insert_tgz_archive(data.as_slice(), subdomain, version)?;
|
.insert_tgz_archive(data.as_slice(), &subdomain, version)?;
|
||||||
} else if data.starts_with(&hex!("504b0304")) {
|
} else if data.starts_with(&hex!("504b0304")) {
|
||||||
talon.storage.insert_zip_archive(
|
talon.storage.insert_zip_archive(
|
||||||
Cursor::new(data.as_slice()),
|
Cursor::new(data.as_slice()),
|
||||||
subdomain,
|
&subdomain,
|
||||||
version,
|
version,
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
|
@ -436,26 +396,20 @@ impl TalonApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validata fallback path
|
// Validata fallback path
|
||||||
if let Some(fallback) = &fallback {
|
if let Some(fallback) = &fallback.0 {
|
||||||
if let Err(e) =
|
if let Err(e) =
|
||||||
talon
|
talon
|
||||||
.storage
|
.storage
|
||||||
.get_file(subdomain, version, fallback, &Default::default())
|
.get_file(&subdomain, version, fallback, &Default::default())
|
||||||
{
|
{
|
||||||
return Err(Error::from(ApiError::InvalidFallback(e.to_string())));
|
return Err(Error::from(ApiError::InvalidFallback(e.to_string())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
};
|
||||||
|
|
||||||
let t2 = talon.clone();
|
match try_insert() {
|
||||||
let sd = subdomain.clone();
|
Ok(()) => {
|
||||||
|
|
||||||
match tokio::task::spawn_blocking(move || try_insert(&t2, data, &sd, version, fallback.0))
|
|
||||||
.await
|
|
||||||
.map_err(|e| Error::from(ApiError::from(e)))
|
|
||||||
{
|
|
||||||
Ok(Ok(())) => {
|
|
||||||
talon.db.update_website(
|
talon.db.update_website(
|
||||||
&subdomain,
|
&subdomain,
|
||||||
db::model::WebsiteUpdate {
|
db::model::WebsiteUpdate {
|
||||||
|
@ -465,7 +419,7 @@ impl TalonApi {
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(e) | Ok(Err(e)) => {
|
Err(e) => {
|
||||||
// Remove the bad version and decrement the id counter
|
// Remove the bad version and decrement the id counter
|
||||||
let _ = talon.db.delete_version(&subdomain, version, false);
|
let _ = talon.db.delete_version(&subdomain, version, false);
|
||||||
let _ = talon.db.decrement_vid(&subdomain, version);
|
let _ = talon.db.decrement_vid(&subdomain, version);
|
||||||
|
|
|
@ -208,7 +208,6 @@ impl Db {
|
||||||
w.source_url = website.source_url.unwrap_or(w.source_url);
|
w.source_url = website.source_url.unwrap_or(w.source_url);
|
||||||
w.source_icon = website.source_icon.unwrap_or(w.source_icon);
|
w.source_icon = website.source_icon.unwrap_or(w.source_icon);
|
||||||
w.has_icon = website.has_icon.unwrap_or(w.has_icon);
|
w.has_icon = website.has_icon.unwrap_or(w.has_icon);
|
||||||
w.updated_at = Some(OffsetDateTime::now_utc());
|
|
||||||
|
|
||||||
rmp_serde::to_vec(&w).ok()
|
rmp_serde::to_vec(&w).ok()
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,19 +29,13 @@ pub struct Website {
|
||||||
/// Does the website have an icon?
|
/// Does the website have an icon?
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub has_icon: bool,
|
pub has_icon: bool,
|
||||||
/// Website update date
|
|
||||||
#[serde(default)]
|
|
||||||
pub updated_at: Option<OffsetDateTime>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Website {
|
impl Default for Website {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let created_at = OffsetDateTime::now_utc();
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
name: Default::default(),
|
name: Default::default(),
|
||||||
created_at,
|
created_at: OffsetDateTime::now_utc(),
|
||||||
updated_at: Some(created_at),
|
|
||||||
latest_version: Default::default(),
|
latest_version: Default::default(),
|
||||||
color: Default::default(),
|
color: Default::default(),
|
||||||
visibility: Default::default(),
|
visibility: Default::default(),
|
||||||
|
@ -53,12 +47,6 @@ impl Default for Website {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Website {
|
|
||||||
pub fn updated_at(&self) -> OffsetDateTime {
|
|
||||||
self.updated_at.unwrap_or(self.created_at)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update a website in the database with the contained values
|
/// Update a website in the database with the contained values
|
||||||
///
|
///
|
||||||
/// Values set to `None` remain unchanged.
|
/// Values set to `None` remain unchanged.
|
||||||
|
|
|
@ -37,7 +37,7 @@ impl ResponseError for ImagesError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const IMAGE_SIZE: u32 = 48;
|
const IMAGE_SIZE: u32 = 32;
|
||||||
const MAX_IMAGE_SIZE: u32 = 4000;
|
const MAX_IMAGE_SIZE: u32 = 4000;
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, ImagesError>;
|
type Result<T> = std::result::Result<T, ImagesError>;
|
||||||
|
|
4
tests/fixtures/mod.rs
vendored
4
tests/fixtures/mod.rs
vendored
|
@ -75,7 +75,6 @@ fn insert_websites(db: &Db) {
|
||||||
&Website {
|
&Website {
|
||||||
name: "ThetaDev".to_owned(),
|
name: "ThetaDev".to_owned(),
|
||||||
created_at: datetime!(2023-02-18 16:30 +0),
|
created_at: datetime!(2023-02-18 16:30 +0),
|
||||||
updated_at: Some(datetime!(2023-02-18 16:30 +0)),
|
|
||||||
latest_version: Some(2),
|
latest_version: Some(2),
|
||||||
color: Some(2068974),
|
color: Some(2068974),
|
||||||
visibility: talon::model::Visibility::Featured,
|
visibility: talon::model::Visibility::Featured,
|
||||||
|
@ -88,7 +87,6 @@ fn insert_websites(db: &Db) {
|
||||||
&Website {
|
&Website {
|
||||||
name: "Spotify-Gender-Ex".to_owned(),
|
name: "Spotify-Gender-Ex".to_owned(),
|
||||||
created_at: datetime!(2023-02-18 16:30 +0),
|
created_at: datetime!(2023-02-18 16:30 +0),
|
||||||
updated_at: Some(datetime!(2023-02-18 16:30 +0)),
|
|
||||||
latest_version: Some(1),
|
latest_version: Some(1),
|
||||||
color: Some(1947988),
|
color: Some(1947988),
|
||||||
visibility: talon::model::Visibility::Featured,
|
visibility: talon::model::Visibility::Featured,
|
||||||
|
@ -103,7 +101,6 @@ fn insert_websites(db: &Db) {
|
||||||
&Website {
|
&Website {
|
||||||
name: "RustyPipe".to_owned(),
|
name: "RustyPipe".to_owned(),
|
||||||
created_at: datetime!(2023-02-20 18:30 +0),
|
created_at: datetime!(2023-02-20 18:30 +0),
|
||||||
updated_at: Some(datetime!(2023-02-20 18:30 +0)),
|
|
||||||
latest_version: Some(1),
|
latest_version: Some(1),
|
||||||
color: Some(7943647),
|
color: Some(7943647),
|
||||||
visibility: talon::model::Visibility::Featured,
|
visibility: talon::model::Visibility::Featured,
|
||||||
|
@ -118,7 +115,6 @@ fn insert_websites(db: &Db) {
|
||||||
&Website {
|
&Website {
|
||||||
name: "SvelteKit SPA".to_owned(),
|
name: "SvelteKit SPA".to_owned(),
|
||||||
created_at: datetime!(2023-03-03 22:00 +0),
|
created_at: datetime!(2023-03-03 22:00 +0),
|
||||||
updated_at: Some(datetime!(2023-03-03 22:00 +0)),
|
|
||||||
latest_version: Some(1),
|
latest_version: Some(1),
|
||||||
color: Some(16727552),
|
color: Some(16727552),
|
||||||
visibility: talon::model::Visibility::Hidden,
|
visibility: talon::model::Visibility::Hidden,
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
source: tests/tests.rs
|
source: tests/tests.rs
|
||||||
expression: data
|
expression: data
|
||||||
---
|
---
|
||||||
{"type":"website","key":"rustypipe","value":{"name":"RustyPipe","created_at":[2023,51,18,30,0,0,0,0,0],"latest_version":1,"color":7943647,"visibility":"featured","source_url":"https://code.thetadev.de/ThetaDev/rustypipe","source_icon":"gitea","vid_count":1,"has_icon":false,"updated_at":[2023,51,18,30,0,0,0,0,0]}}
|
{"type":"website","key":"rustypipe","value":{"name":"RustyPipe","created_at":[2023,51,18,30,0,0,0,0,0],"latest_version":1,"color":7943647,"visibility":"featured","source_url":"https://code.thetadev.de/ThetaDev/rustypipe","source_icon":"gitea","vid_count":1,"has_icon":false}}
|
||||||
{"type":"website","key":"spa","value":{"name":"SvelteKit SPA","created_at":[2023,62,22,0,0,0,0,0,0],"latest_version":1,"color":16727552,"visibility":"hidden","source_url":null,"source_icon":null,"vid_count":1,"has_icon":false,"updated_at":[2023,62,22,0,0,0,0,0,0]}}
|
{"type":"website","key":"spa","value":{"name":"SvelteKit SPA","created_at":[2023,62,22,0,0,0,0,0,0],"latest_version":1,"color":16727552,"visibility":"hidden","source_url":null,"source_icon":null,"vid_count":1,"has_icon":false}}
|
||||||
{"type":"website","key":"spotify-gender-ex","value":{"name":"Spotify-Gender-Ex","created_at":[2023,49,16,30,0,0,0,0,0],"latest_version":1,"color":1947988,"visibility":"featured","source_url":"https://github.com/Theta-Dev/Spotify-Gender-Ex","source_icon":"github","vid_count":1,"has_icon":false,"updated_at":[2023,49,16,30,0,0,0,0,0]}}
|
{"type":"website","key":"spotify-gender-ex","value":{"name":"Spotify-Gender-Ex","created_at":[2023,49,16,30,0,0,0,0,0],"latest_version":1,"color":1947988,"visibility":"featured","source_url":"https://github.com/Theta-Dev/Spotify-Gender-Ex","source_icon":"github","vid_count":1,"has_icon":false}}
|
||||||
{"type":"version","key":"rustypipe:1","value":{"created_at":[2023,51,18,30,0,0,0,0,0],"data":{},"fallback":null,"spa":false}}
|
{"type":"version","key":"rustypipe:1","value":{"created_at":[2023,51,18,30,0,0,0,0,0],"data":{},"fallback":null,"spa":false}}
|
||||||
{"type":"version","key":"spa:1","value":{"created_at":[2023,62,22,0,0,0,0,0,0],"data":{},"fallback":"200.html","spa":true}}
|
{"type":"version","key":"spa:1","value":{"created_at":[2023,62,22,0,0,0,0,0,0],"data":{},"fallback":"200.html","spa":true}}
|
||||||
{"type":"version","key":"spotify-gender-ex:1","value":{"created_at":[2023,49,16,30,0,0,0,0,0],"data":{},"fallback":null,"spa":false}}
|
{"type":"version","key":"spotify-gender-ex:1","value":{"created_at":[2023,49,16,30,0,0,0,0,0],"data":{},"fallback":null,"spa":false}}
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
source: tests/tests.rs
|
source: tests/tests.rs
|
||||||
expression: data
|
expression: data
|
||||||
---
|
---
|
||||||
{"type":"website","key":"-","value":{"name":"ThetaDev","created_at":[2023,49,16,30,0,0,0,0,0],"latest_version":2,"color":2068974,"visibility":"featured","source_url":null,"source_icon":null,"vid_count":2,"has_icon":false,"updated_at":[2023,49,16,30,0,0,0,0,0]}}
|
{"type":"website","key":"-","value":{"name":"ThetaDev","created_at":[2023,49,16,30,0,0,0,0,0],"latest_version":2,"color":2068974,"visibility":"featured","source_url":null,"source_icon":null,"vid_count":2,"has_icon":false}}
|
||||||
{"type":"website","key":"rustypipe","value":{"name":"RustyPipe","created_at":[2023,51,18,30,0,0,0,0,0],"latest_version":1,"color":7943647,"visibility":"featured","source_url":"https://code.thetadev.de/ThetaDev/rustypipe","source_icon":"gitea","vid_count":1,"has_icon":false,"updated_at":[2023,51,18,30,0,0,0,0,0]}}
|
{"type":"website","key":"rustypipe","value":{"name":"RustyPipe","created_at":[2023,51,18,30,0,0,0,0,0],"latest_version":1,"color":7943647,"visibility":"featured","source_url":"https://code.thetadev.de/ThetaDev/rustypipe","source_icon":"gitea","vid_count":1,"has_icon":false}}
|
||||||
{"type":"website","key":"spa","value":{"name":"SvelteKit SPA","created_at":[2023,62,22,0,0,0,0,0,0],"latest_version":1,"color":16727552,"visibility":"hidden","source_url":null,"source_icon":null,"vid_count":1,"has_icon":false,"updated_at":[2023,62,22,0,0,0,0,0,0]}}
|
{"type":"website","key":"spa","value":{"name":"SvelteKit SPA","created_at":[2023,62,22,0,0,0,0,0,0],"latest_version":1,"color":16727552,"visibility":"hidden","source_url":null,"source_icon":null,"vid_count":1,"has_icon":false}}
|
||||||
{"type":"website","key":"spotify-gender-ex","value":{"name":"Spotify-Gender-Ex","created_at":[2023,49,16,30,0,0,0,0,0],"latest_version":1,"color":1947988,"visibility":"featured","source_url":"https://github.com/Theta-Dev/Spotify-Gender-Ex","source_icon":"github","vid_count":1,"has_icon":false,"updated_at":[2023,49,16,30,0,0,0,0,0]}}
|
{"type":"website","key":"spotify-gender-ex","value":{"name":"Spotify-Gender-Ex","created_at":[2023,49,16,30,0,0,0,0,0],"latest_version":1,"color":1947988,"visibility":"featured","source_url":"https://github.com/Theta-Dev/Spotify-Gender-Ex","source_icon":"github","vid_count":1,"has_icon":false}}
|
||||||
{"type":"version","key":"-:1","value":{"created_at":[2023,49,16,30,0,0,0,0,0],"data":{"Deployed by":"https://github.com/Theta-Dev/Talon/actions/runs/1352014628","Version":"v0.1.0"},"fallback":null,"spa":false}}
|
{"type":"version","key":"-:1","value":{"created_at":[2023,49,16,30,0,0,0,0,0],"data":{"Deployed by":"https://github.com/Theta-Dev/Talon/actions/runs/1352014628","Version":"v0.1.0"},"fallback":null,"spa":false}}
|
||||||
{"type":"version","key":"-:2","value":{"created_at":[2023,49,16,52,0,0,0,0,0],"data":{"Deployed by":"https://github.com/Theta-Dev/Talon/actions/runs/1354755231","Version":"v0.1.1"},"fallback":null,"spa":false}}
|
{"type":"version","key":"-:2","value":{"created_at":[2023,49,16,52,0,0,0,0,0],"data":{"Deployed by":"https://github.com/Theta-Dev/Talon/actions/runs/1354755231","Version":"v0.1.1"},"fallback":null,"spa":false}}
|
||||||
{"type":"version","key":"rustypipe:1","value":{"created_at":[2023,51,18,30,0,0,0,0,0],"data":{},"fallback":null,"spa":false}}
|
{"type":"version","key":"rustypipe:1","value":{"created_at":[2023,51,18,30,0,0,0,0,0],"data":{},"fallback":null,"spa":false}}
|
||||||
|
|
|
@ -13,7 +13,6 @@ expression: "vec![ws1, ws2, ws3]"
|
||||||
source_icon: None,
|
source_icon: None,
|
||||||
vid_count: 2,
|
vid_count: 2,
|
||||||
has_icon: false,
|
has_icon: false,
|
||||||
updated_at: Some((2023, 49, 16, 30, 0, 0, 0, 0, 0)),
|
|
||||||
),
|
),
|
||||||
Website(
|
Website(
|
||||||
name: "Spotify-Gender-Ex",
|
name: "Spotify-Gender-Ex",
|
||||||
|
@ -25,7 +24,6 @@ expression: "vec![ws1, ws2, ws3]"
|
||||||
source_icon: Some(github),
|
source_icon: Some(github),
|
||||||
vid_count: 1,
|
vid_count: 1,
|
||||||
has_icon: false,
|
has_icon: false,
|
||||||
updated_at: Some((2023, 49, 16, 30, 0, 0, 0, 0, 0)),
|
|
||||||
),
|
),
|
||||||
Website(
|
Website(
|
||||||
name: "RustyPipe",
|
name: "RustyPipe",
|
||||||
|
@ -37,6 +35,5 @@ expression: "vec![ws1, ws2, ws3]"
|
||||||
source_icon: Some(gitea),
|
source_icon: Some(gitea),
|
||||||
vid_count: 1,
|
vid_count: 1,
|
||||||
has_icon: false,
|
has_icon: false,
|
||||||
updated_at: Some((2023, 51, 18, 30, 0, 0, 0, 0, 0)),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -13,7 +13,6 @@ expression: websites
|
||||||
source_icon: None,
|
source_icon: None,
|
||||||
vid_count: 2,
|
vid_count: 2,
|
||||||
has_icon: false,
|
has_icon: false,
|
||||||
updated_at: Some((2023, 49, 16, 30, 0, 0, 0, 0, 0)),
|
|
||||||
)),
|
)),
|
||||||
("rustypipe", Website(
|
("rustypipe", Website(
|
||||||
name: "RustyPipe",
|
name: "RustyPipe",
|
||||||
|
@ -25,7 +24,6 @@ expression: websites
|
||||||
source_icon: Some(gitea),
|
source_icon: Some(gitea),
|
||||||
vid_count: 1,
|
vid_count: 1,
|
||||||
has_icon: false,
|
has_icon: false,
|
||||||
updated_at: Some((2023, 51, 18, 30, 0, 0, 0, 0, 0)),
|
|
||||||
)),
|
)),
|
||||||
("spa", Website(
|
("spa", Website(
|
||||||
name: "SvelteKit SPA",
|
name: "SvelteKit SPA",
|
||||||
|
@ -37,7 +35,6 @@ expression: websites
|
||||||
source_icon: None,
|
source_icon: None,
|
||||||
vid_count: 1,
|
vid_count: 1,
|
||||||
has_icon: false,
|
has_icon: false,
|
||||||
updated_at: Some((2023, 62, 22, 0, 0, 0, 0, 0, 0)),
|
|
||||||
)),
|
)),
|
||||||
("spotify-gender-ex", Website(
|
("spotify-gender-ex", Website(
|
||||||
name: "Spotify-Gender-Ex",
|
name: "Spotify-Gender-Ex",
|
||||||
|
@ -49,6 +46,5 @@ expression: websites
|
||||||
source_icon: Some(github),
|
source_icon: Some(github),
|
||||||
vid_count: 1,
|
vid_count: 1,
|
||||||
has_icon: false,
|
has_icon: false,
|
||||||
updated_at: Some((2023, 49, 16, 30, 0, 0, 0, 0, 0)),
|
|
||||||
)),
|
)),
|
||||||
]
|
]
|
||||||
|
|
|
@ -12,5 +12,4 @@ Website(
|
||||||
source_icon: Some(link),
|
source_icon: Some(link),
|
||||||
vid_count: 2,
|
vid_count: 2,
|
||||||
has_icon: false,
|
has_icon: false,
|
||||||
updated_at: "[date]",
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,8 +8,6 @@ use rstest::rstest;
|
||||||
use fixtures::*;
|
use fixtures::*;
|
||||||
use talon::db::{Db, DbError};
|
use talon::db::{Db, DbError};
|
||||||
|
|
||||||
const ICON_SIZE: u32 = 48;
|
|
||||||
|
|
||||||
mod database {
|
mod database {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -85,7 +83,7 @@ mod database {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let website = db.get_website(SUBDOMAIN_1).unwrap();
|
let website = db.get_website(SUBDOMAIN_1).unwrap();
|
||||||
insta::assert_ron_snapshot!(website, {".updated_at" => "[date]"});
|
insta::assert_ron_snapshot!(website);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
|
@ -615,8 +613,8 @@ mod icons {
|
||||||
assert!(stored_path.is_file());
|
assert!(stored_path.is_file());
|
||||||
|
|
||||||
let stored_img = ImageReader::open(&stored_path).unwrap().decode().unwrap();
|
let stored_img = ImageReader::open(&stored_path).unwrap().decode().unwrap();
|
||||||
assert_eq!(stored_img.height(), ICON_SIZE);
|
assert_eq!(stored_img.height(), 32);
|
||||||
assert_eq!(stored_img.width(), ICON_SIZE);
|
assert_eq!(stored_img.width(), 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -858,7 +856,7 @@ mod api {
|
||||||
resp.assert_status_is_ok();
|
resp.assert_status_is_ok();
|
||||||
|
|
||||||
let ws = tln.db.get_website("test").unwrap();
|
let ws = tln.db.get_website("test").unwrap();
|
||||||
insta::assert_ron_snapshot!(ws, {".created_at" => "[date]", ".updated_at" => "[date]"}, @r###"
|
insta::assert_ron_snapshot!(ws, {".created_at" => "[date]"}, @r###"
|
||||||
Website(
|
Website(
|
||||||
name: "Test",
|
name: "Test",
|
||||||
created_at: "[date]",
|
created_at: "[date]",
|
||||||
|
@ -869,7 +867,6 @@ mod api {
|
||||||
source_icon: Some(git),
|
source_icon: Some(git),
|
||||||
vid_count: 0,
|
vid_count: 0,
|
||||||
has_icon: false,
|
has_icon: false,
|
||||||
updated_at: "[date]",
|
|
||||||
)
|
)
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
@ -914,7 +911,7 @@ mod api {
|
||||||
resp.assert_status_is_ok();
|
resp.assert_status_is_ok();
|
||||||
|
|
||||||
let ws = tln.db.get_website("-").unwrap();
|
let ws = tln.db.get_website("-").unwrap();
|
||||||
insta::assert_ron_snapshot!(ws, {".updated_at" => "[date]"}, @r###"
|
insta::assert_ron_snapshot!(ws, @r###"
|
||||||
Website(
|
Website(
|
||||||
name: "Test",
|
name: "Test",
|
||||||
created_at: (2023, 49, 16, 30, 0, 0, 0, 0, 0),
|
created_at: (2023, 49, 16, 30, 0, 0, 0, 0, 0),
|
||||||
|
@ -925,7 +922,6 @@ mod api {
|
||||||
source_icon: Some(git),
|
source_icon: Some(git),
|
||||||
vid_count: 2,
|
vid_count: 2,
|
||||||
has_icon: false,
|
has_icon: false,
|
||||||
updated_at: "[date]",
|
|
||||||
)
|
)
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
@ -971,8 +967,8 @@ mod api {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.decode()
|
.decode()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(got_icon.height(), ICON_SIZE);
|
assert_eq!(got_icon.height(), 32);
|
||||||
assert_eq!(got_icon.width(), ICON_SIZE);
|
assert_eq!(got_icon.width(), 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
let currentWebsite: Website;
|
let currentWebsite: Website;
|
||||||
|
|
||||||
currentWebsiteStore.subscribe((ws) => {
|
currentWebsiteStore.subscribe((ws) => {
|
||||||
|
console.log("current ws changed", ws);
|
||||||
currentWebsite = ws;
|
currentWebsite = ws;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -139,7 +139,7 @@
|
||||||
height: 100%
|
height: 100%
|
||||||
z-index: 999999
|
z-index: 999999
|
||||||
|
|
||||||
padding: 3em 0.4em 0.4em
|
padding: 1em 0.4em
|
||||||
|
|
||||||
display: flex
|
display: flex
|
||||||
flex-direction: column
|
flex-direction: column
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
import PageIcon from "./PageIcon.svelte";
|
import PageIcon from "./PageIcon.svelte";
|
||||||
import {
|
import {
|
||||||
formatDate,
|
formatDate,
|
||||||
getSubdomainAndVersion,
|
|
||||||
getWebsiteVersionUrl,
|
getWebsiteVersionUrl,
|
||||||
isUrl,
|
isUrl,
|
||||||
trimCommit,
|
trimCommit,
|
||||||
|
@ -15,37 +14,28 @@
|
||||||
import Modal from "./Modal.svelte";
|
import Modal from "./Modal.svelte";
|
||||||
import { openModal } from "svelte-modals";
|
import { openModal } from "svelte-modals";
|
||||||
import InstanceInfoModal from "./InstanceInfoModal.svelte";
|
import InstanceInfoModal from "./InstanceInfoModal.svelte";
|
||||||
import { onMount } from "svelte";
|
|
||||||
|
|
||||||
let currentWebsite: Website;
|
let currentWebsite: Website;
|
||||||
currentWebsiteStore.subscribe((ws) => {
|
currentWebsiteStore.subscribe((ws) => {
|
||||||
currentWebsite = ws;
|
currentWebsite = ws;
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentVid: number | null = getSubdomainAndVersion()[1];
|
|
||||||
|
|
||||||
export let isOpen: boolean;
|
export let isOpen: boolean;
|
||||||
|
$: {
|
||||||
onMount(async () => {
|
if (isOpen && currentWebsite) {
|
||||||
const v = await client.websiteSubdomainVersionsGet({
|
client
|
||||||
subdomain: currentWebsite.subdomain,
|
.websiteSubdomainVersionsGet({ subdomain: currentWebsite.subdomain })
|
||||||
});
|
.then((v) => {
|
||||||
|
versions = v;
|
||||||
versions = v;
|
if (v && v.length > 0) {
|
||||||
if (v && v.length > 0) {
|
currentVersion = v[v.length - 1];
|
||||||
latestVersion = v[v.length - 1];
|
}
|
||||||
|
});
|
||||||
if (currentVid !== null) {
|
|
||||||
currentVersion = v.find((v) => v.id == currentVid);
|
|
||||||
} else {
|
|
||||||
currentVersion = latestVersion;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
let versions: Version[] = [];
|
let versions: Version[] = [];
|
||||||
let currentVersion: Version | undefined;
|
let currentVersion: Version = null;
|
||||||
let latestVersion: Version | undefined;
|
|
||||||
|
|
||||||
function getVersionAttr(version: Version): string | null {
|
function getVersionAttr(version: Version): string | null {
|
||||||
return (
|
return (
|
||||||
|
@ -73,14 +63,6 @@
|
||||||
<p class="divider">
|
<p class="divider">
|
||||||
<InlineIcon iconName="question" />
|
<InlineIcon iconName="question" />
|
||||||
Current version #{currentVersion.id}
|
Current version #{currentVersion.id}
|
||||||
|
|
||||||
{#if latestVersion && latestVersion !== currentVersion}
|
|
||||||
<a
|
|
||||||
class="latest-tag"
|
|
||||||
href={getWebsiteVersionUrl(currentWebsite.subdomain, latestVersion.id)}
|
|
||||||
>Latest: #{latestVersion.id}</a
|
|
||||||
>
|
|
||||||
{/if}
|
|
||||||
</p>
|
</p>
|
||||||
<Tag key="Upload date" value={formatDate(currentVersion.createdAt)} />
|
<Tag key="Upload date" value={formatDate(currentVersion.createdAt)} />
|
||||||
|
|
||||||
|
@ -113,9 +95,7 @@
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
Powered by
|
Powered by
|
||||||
<button class="link" on:click={openInstanceInfo}
|
<button on:click={openInstanceInfo}>Talon {talonConfig.version}</button>
|
||||||
>Talon {talonConfig.version}</button
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -131,13 +111,4 @@
|
||||||
font-size: 2em
|
font-size: 2em
|
||||||
margin-left: 0.25em
|
margin-left: 0.25em
|
||||||
|
|
||||||
.latest-tag
|
|
||||||
background-color: lime
|
|
||||||
color: values.$color-text-d1
|
|
||||||
margin: 0 1em
|
|
||||||
overflow: hidden
|
|
||||||
white-space: nowrap
|
|
||||||
padding: 0 0.4em
|
|
||||||
border-radius: 1em
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -6,16 +6,15 @@
|
||||||
font-family: sans-serif
|
font-family: sans-serif
|
||||||
color: values.$color-text
|
color: values.$color-text
|
||||||
|
|
||||||
a, .link
|
a, button
|
||||||
display: inline
|
display: inline
|
||||||
text-decoration: none
|
text-decoration: none
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
background: none
|
background: none
|
||||||
border: none
|
border: none
|
||||||
box-shadow: none
|
box-shadow: none
|
||||||
padding: 0
|
|
||||||
|
|
||||||
.link
|
a
|
||||||
color: var(--talon-color)
|
color: var(--talon-color)
|
||||||
filter: brightness(150%)
|
filter: brightness(150%)
|
||||||
|
|
||||||
|
|
|
@ -13,4 +13,3 @@ $color-base-2: color.scale($color-base, $lightness: 20%)
|
||||||
$color-primary-light: color.scale($color-primary, $lightness: 15%)
|
$color-primary-light: color.scale($color-primary, $lightness: 15%)
|
||||||
$color-primary-dark: color.scale($color-primary, $lightness: -15%)
|
$color-primary-dark: color.scale($color-primary, $lightness: -15%)
|
||||||
$color-text-1: color.scale($color-text, $lightness: -15%)
|
$color-text-1: color.scale($color-text, $lightness: -15%)
|
||||||
$color-text-d1: color.scale($color-base, $lightness: -20%)
|
|
||||||
|
|
|
@ -21,29 +21,6 @@ export function getSubdomain(): string {
|
||||||
return "-";
|
return "-";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSubdomainAndVersion(): [string, number | null] {
|
|
||||||
const hn = window.location.hostname;
|
|
||||||
const rd_noport = talonConfig.root_domain.split(":", 1)[0];
|
|
||||||
|
|
||||||
if (hn.endsWith("." + rd_noport)) {
|
|
||||||
const subdomainSplit = hn
|
|
||||||
.substring(0, hn.length - rd_noport.length - 1)
|
|
||||||
.split("--", 2);
|
|
||||||
const subdomain = subdomainSplit[0];
|
|
||||||
|
|
||||||
let version =
|
|
||||||
subdomainSplit.length > 1 ? parseInt(subdomainSplit[1].replace(/^v/, "")) : null;
|
|
||||||
if (Number.isNaN(version)) version = null;
|
|
||||||
|
|
||||||
if (subdomain === "x") {
|
|
||||||
return ["-", version];
|
|
||||||
} else {
|
|
||||||
return [subdomain, version];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ["-", null];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getWebsiteUrl(subdomain: string): string {
|
export function getWebsiteUrl(subdomain: string): string {
|
||||||
const proto = window.location.protocol;
|
const proto = window.location.protocol;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue