Compare commits

..

2 commits

Author SHA1 Message Date
0352989083 refactor: use sequential version ids per website
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-03-04 18:49:47 +01:00
f9c82e5601 feat: add version_files api endpoint 2023-03-04 17:40:58 +01:00
14 changed files with 1849 additions and 5593 deletions

View file

@ -2,7 +2,7 @@
**static site management system** **static site management system**
![CI status](https://ci.thetadev.de/api/badges/ThetaDev/Talon/status.svg) [![CI status](https://ci.thetadev.de/api/badges/ThetaDev/Talon/status.svg)](https://ci.thetadev.de/ThetaDev/Talon)
--- ---

View file

@ -211,58 +211,66 @@ impl TalonApi {
} }
/// Get version /// Get version
#[oai(path = "/website/:subdomain/version/:id", method = "get")] #[oai(path = "/website/:subdomain/version/:version", method = "get")]
async fn version_get( async fn version_get(
&self, &self,
talon: Data<&Talon>, talon: Data<&Talon>,
subdomain: Path<String>, subdomain: Path<String>,
id: Path<u32>, version: Path<u32>,
) -> Result<Json<Version>> { ) -> Result<Json<Version>> {
talon talon
.db .db
.get_version(&subdomain, *id) .get_version(&subdomain, *version)
.map(|v| Json(Version::from((*id, v)))) .map(|v| Json(Version::from((*version, v))))
.map_err(Error::from)
}
/// Get version files
#[oai(path = "/website/:subdomain/version/:version/files", method = "get")]
async fn version_files(
&self,
talon: Data<&Talon>,
subdomain: Path<String>,
version: Path<u32>,
) -> Result<Json<Vec<String>>> {
talon.db.version_exists(&subdomain, *version)?;
talon
.db
.get_version_files(&subdomain, *version)
.map(|r| r.map(|f| f.0))
.collect::<Result<Vec<_>, _>>()
.map(Json)
.map_err(Error::from) .map_err(Error::from)
} }
/// Delete version /// Delete version
#[oai(path = "/website/:subdomain/version/:id", method = "delete")] #[oai(path = "/website/:subdomain/version/:version", method = "delete")]
async fn version_delete( async fn version_delete(
&self, &self,
auth: ApiKeyAuthorization, auth: ApiKeyAuthorization,
talon: Data<&Talon>, talon: Data<&Talon>,
subdomain: Path<String>, subdomain: Path<String>,
id: Path<u32>, version: Path<u32>,
) -> Result<()> { ) -> Result<()> {
auth.check_subdomain(&subdomain, Access::Modify)?; auth.check_subdomain(&subdomain, Access::Modify)?;
talon.db.delete_version(&subdomain, *id, true)?; talon.db.delete_version(&subdomain, *version, true)?;
Ok(()) Ok(())
} }
/// Insert a new version into the database
fn insert_version( fn insert_version(
talon: &Talon, talon: &Talon,
subdomain: &str, subdomain: &str,
id: u32,
fallback: Option<String>, fallback: Option<String>,
spa: bool, spa: bool,
mut version_data: BTreeMap<String, String>, mut version_data: BTreeMap<String, String>,
) -> Result<()> { ) -> Result<u32> {
version_data.remove("fallback"); version_data.remove("fallback");
version_data.remove("spa"); version_data.remove("spa");
// Validata fallback path let id = talon.db.insert_version(
if let Some(fallback) = &fallback {
if let Err(e) = talon.storage.get_file(id, fallback, &Default::default()) {
// Remove the uploaded files of the bad version
let _ = talon.db.delete_version(subdomain, id, false);
return Err(ApiError::InvalidFallback(e.to_string()).into());
}
}
talon.db.insert_version(
subdomain, subdomain,
id,
&db::model::Version { &db::model::Version {
data: version_data, data: version_data,
fallback, fallback,
@ -270,10 +278,33 @@ impl TalonApi {
..Default::default() ..Default::default()
}, },
)?; )?;
Ok(id)
}
/// Set the given version as the most recent one
fn finalize_version(
talon: &Talon,
subdomain: &str,
version: u32,
fallback: Option<&str>,
) -> Result<()> {
// Validata fallback path
if let Some(fallback) = fallback {
if let Err(e) =
talon
.storage
.get_file(subdomain, version, fallback, &Default::default())
{
// Remove the bad version
let _ = talon.db.delete_version(subdomain, version, false);
return Err(ApiError::InvalidFallback(e.to_string()).into());
}
}
talon.db.update_website( talon.db.update_website(
subdomain, subdomain,
db::model::WebsiteUpdate { db::model::WebsiteUpdate {
latest_version: Some(Some(id)), latest_version: Some(Some(version)),
..Default::default() ..Default::default()
}, },
)?; )?;
@ -303,11 +334,12 @@ impl TalonApi {
data: Binary<Vec<u8>>, data: Binary<Vec<u8>>,
) -> Result<()> { ) -> Result<()> {
auth.check_subdomain(&subdomain, Access::Upload)?; auth.check_subdomain(&subdomain, Access::Upload)?;
let vid = talon.db.new_version_id()?; let version =
Self::insert_version(&talon, &subdomain, fallback.clone(), spa.0, version_data.0)?;
talon talon
.storage .storage
.insert_zip_archive(Cursor::new(data.as_slice()), vid)?; .insert_zip_archive(Cursor::new(data.as_slice()), &subdomain, version)?;
Self::insert_version(&talon, &subdomain, vid, fallback.0, spa.0, version_data.0) Self::finalize_version(&talon, &subdomain, version, fallback.as_deref())
} }
/// Upload a new version (.tar.gz archive) /// Upload a new version (.tar.gz archive)
@ -333,8 +365,11 @@ impl TalonApi {
data: Binary<Vec<u8>>, data: Binary<Vec<u8>>,
) -> Result<()> { ) -> Result<()> {
auth.check_subdomain(&subdomain, Access::Upload)?; auth.check_subdomain(&subdomain, Access::Upload)?;
let vid = talon.db.new_version_id()?; let version =
talon.storage.insert_tgz_archive(data.as_slice(), vid)?; Self::insert_version(&talon, &subdomain, fallback.clone(), spa.0, version_data.0)?;
Self::insert_version(&talon, &subdomain, vid, fallback.0, spa.0, version_data.0) talon
.storage
.insert_tgz_archive(data.as_slice(), &subdomain, version)?;
Self::finalize_version(&talon, &subdomain, version, fallback.as_deref())
} }
} }

View file

@ -259,14 +259,6 @@ impl Db {
String::from_utf8(key).map_err(|e| DbError::Other(format!("could not parse key: {e}"))) String::from_utf8(key).map_err(|e| DbError::Other(format!("could not parse key: {e}")))
} }
fn split_key(key: Vec<u8>) -> Result<(String, String)> {
let key_str = Self::key_to_string(key)?;
key_str
.split_once(':')
.map(|(id, p)| (id.to_owned(), p.to_owned()))
.ok_or_else(|| DbError::Other(format!("invalid key: {key_str}")))
}
fn split_version_key(key: Vec<u8>) -> Result<(String, u32)> { fn split_version_key(key: Vec<u8>) -> Result<(String, u32)> {
let key_str = Self::key_to_string(key)?; let key_str = Self::key_to_string(key)?;
key_str key_str
@ -275,47 +267,68 @@ impl Db {
.ok_or_else(|| DbError::Other(format!("invalid key: {key_str}"))) .ok_or_else(|| DbError::Other(format!("invalid key: {key_str}")))
} }
fn increment(old: Option<&[u8]>) -> Option<Vec<u8>> { fn split_file_key(key: Vec<u8>) -> Result<(String, String)> {
let number = match old { let key_str = Self::key_to_string(key)?;
Some(bytes) => { let mut parts = key_str.split(':');
let array: [u8; 4] = bytes.try_into().unwrap(); parts.next(); // Skip subdomain part
let number = u32::from_be_bytes(array);
number + 1 match (parts.next(), parts.next()) {
} (Some(id), Some(p)) => Ok((id.to_owned(), p.to_owned())),
None => 1, _ => Err(DbError::Other(format!("invalid key: {key_str}"))),
}; }
Some(number.to_be_bytes().to_vec()) }
/// Returns an error if the website does not exist
pub fn version_exists(&self, subdomain: &str, id: u32) -> Result<()> {
let key = Self::version_key(subdomain, id);
if !self.i.versions.contains_key(&key)? {
Err(DbError::NotExists("version", key))
} else {
Ok(())
}
} }
/// Get a version from the database /// Get a version from the database
pub fn get_version(&self, subdomain: &str, id: u32) -> Result<Version> { pub fn get_version(&self, subdomain: &str, id: u32) -> Result<Version> {
let data = self.i.versions.get(Self::version_key(subdomain, id))?; let key = Self::version_key(subdomain, id);
data.and_then(|data| rmp_serde::from_slice::<Version>(data.as_ref()).ok())
.ok_or_else(|| DbError::NotExists("version", subdomain.to_owned()))
}
/// Get a new unique version id let data = self.i.versions.get(&key)?;
pub fn new_version_id(&self) -> Result<u32> { data.and_then(|data| rmp_serde::from_slice::<Version>(data.as_ref()).ok())
Ok(u32::from_be_bytes( .ok_or_else(|| DbError::NotExists("version", key))
self.i
.db
.update_and_fetch("vid_count", Self::increment)?
.unwrap()
.as_ref()
.try_into()
.unwrap(),
))
} }
/// Insert a new version into the database /// Insert a new version into the database
pub fn insert_version(&self, subdomain: &str, id: u32, version: &Version) -> Result<()> { ///
/// Returns the ID of the new version
pub fn insert_version(&self, subdomain: &str, version: &Version) -> Result<u32> {
let ws = self
.i
.websites
.update_and_fetch(subdomain, |data| match data {
Some(data) => match rmp_serde::from_slice::<Website>(data) {
Ok(mut w) => {
w.vid_count += 1;
rmp_serde::to_vec(&w).ok()
}
Err(_) => None,
},
None => todo!(),
})?
.and_then(|data| rmp_serde::from_slice::<Website>(&data).ok());
let id = match ws {
Some(ws) => ws.vid_count,
None => return Err(DbError::NotExists("website", subdomain.to_owned())),
};
let key = Self::version_key(subdomain, id); let key = Self::version_key(subdomain, id);
let data = rmp_serde::to_vec(version)?; let data = rmp_serde::to_vec(version)?;
self.i self.i
.versions .versions
.compare_and_swap(&key, None::<&[u8]>, Some(data))? .compare_and_swap(&key, None::<&[u8]>, Some(data))?
.map_err(|_| DbError::Exists("version", key))?; .map_err(|_| DbError::Exists("version", key))?;
Ok(()) Ok(id)
} }
/// internal method for deleting a version from the database /// internal method for deleting a version from the database
@ -323,10 +336,10 @@ impl Db {
/// this method does not lock the db or update the associated website /// this method does not lock the db or update the associated website
fn _delete_version(&self, subdomain: &str, id: u32, should_exist: bool) -> Result<()> { fn _delete_version(&self, subdomain: &str, id: u32, should_exist: bool) -> Result<()> {
// Remove all files associated with the version // Remove all files associated with the version
for f in self.get_version_files(id) { for f in self.get_version_files(subdomain, id) {
match f { match f {
Ok((path, _)) => { Ok((path, _)) => {
self.delete_file(id, &path, false)?; self.delete_file(subdomain, id, &path, false)?;
} }
Err(DbError::Sled(e)) => return Err(DbError::Sled(e)), Err(DbError::Sled(e)) => return Err(DbError::Sled(e)),
Err(_) => {} Err(_) => {}
@ -397,19 +410,24 @@ impl Db {
}) })
} }
fn file_key(version: u32, path: &str) -> String { fn file_key(subdomain: &str, version: u32, path: &str) -> String {
format!("{version}:{path}") format!("{subdomain}:{version}:{path}")
} }
/// Get the hash of a file in the database /// Get the hash of a file in the database
pub fn get_file_opt(&self, version: u32, path: &str) -> Result<Option<Vec<u8>>> { pub fn get_file_opt(
let key = Self::file_key(version, path); &self,
subdomain: &str,
version: u32,
path: &str,
) -> Result<Option<Vec<u8>>> {
let key = Self::file_key(subdomain, version, path);
Ok(self.i.files.get(key)?.map(|hash| hash.to_vec())) Ok(self.i.files.get(key)?.map(|hash| hash.to_vec()))
} }
/// Get the hash of a file in the database /// Get the hash of a file in the database
pub fn get_file(&self, version: u32, path: &str) -> Result<Vec<u8>> { pub fn get_file(&self, subdomain: &str, version: u32, path: &str) -> Result<Vec<u8>> {
let key = Self::file_key(version, path); let key = Self::file_key(subdomain, version, path);
match self.i.files.get(&key)? { match self.i.files.get(&key)? {
Some(hash) => Ok(hash.to_vec()), Some(hash) => Ok(hash.to_vec()),
None => Err(DbError::NotExists("file", key)), None => Err(DbError::NotExists("file", key)),
@ -417,8 +435,14 @@ impl Db {
} }
/// Insert a file into the database /// Insert a file into the database
pub fn insert_file(&self, version: u32, path: &str, hash: &[u8]) -> Result<()> { pub fn insert_file(
let key = Self::file_key(version, path); &self,
subdomain: &str,
version: u32,
path: &str,
hash: &[u8],
) -> Result<()> {
let key = Self::file_key(subdomain, version, path);
self.i self.i
.files .files
.compare_and_swap(&key, None::<&[u8]>, Some(hash))? .compare_and_swap(&key, None::<&[u8]>, Some(hash))?
@ -426,8 +450,14 @@ impl Db {
} }
/// Delete a file in the database /// Delete a file in the database
pub fn delete_file(&self, version: u32, path: &str, should_exist: bool) -> Result<()> { pub fn delete_file(
let key = Self::file_key(version, path); &self,
subdomain: &str,
version: u32,
path: &str,
should_exist: bool,
) -> Result<()> {
let key = Self::file_key(subdomain, version, path);
let res = self.i.files.remove(&key)?; let res = self.i.files.remove(&key)?;
if should_exist && res.is_none() { if should_exist && res.is_none() {
@ -442,13 +472,14 @@ impl Db {
/// Result: Tuples of file path and hash /// Result: Tuples of file path and hash
pub fn get_version_files( pub fn get_version_files(
&self, &self,
id: u32, subdomain: &str,
version: u32,
) -> impl DoubleEndedIterator<Item = Result<(String, Vec<u8>)>> { ) -> impl DoubleEndedIterator<Item = Result<(String, Vec<u8>)>> {
let key = Self::file_key(id, ""); let key = Self::file_key(subdomain, version, "");
self.i.files.scan_prefix(key).map(|r| { self.i.files.scan_prefix(key).map(|r| {
r.map_err(DbError::from).and_then(|(k, v)| { r.map_err(DbError::from).and_then(|(k, v)| {
let (_, path) = Self::split_key(k.to_vec())?; let (_, path) = Self::split_file_key(k.to_vec())?;
Ok((path, v.to_vec())) Ok((path, v.to_vec()))
}) })
}) })

View file

@ -22,6 +22,10 @@ pub struct Website {
pub source_url: Option<String>, pub source_url: Option<String>,
/// Icon for the source link /// Icon for the source link
pub source_icon: Option<SourceIcon>, pub source_icon: Option<SourceIcon>,
/// Version ID counter
///
/// value + 1 will be the next version ID
pub vid_count: u32,
} }
impl Default for Website { impl Default for Website {
@ -34,6 +38,7 @@ impl Default for Website {
visibility: Default::default(), visibility: Default::default(),
source_url: Default::default(), source_url: Default::default(),
source_icon: Default::default(), source_icon: Default::default(),
vid_count: Default::default(),
} }
} }
} }

View file

@ -36,26 +36,34 @@ pub async fn page(request: &Request, talon: Data<&Talon>) -> Result<Response> {
let ws = talon.db.get_website(subdomain)?; let ws = talon.db.get_website(subdomain)?;
let vid = ws.latest_version.ok_or(PageError::NoVersion)?; let vid = ws.latest_version.ok_or(PageError::NoVersion)?;
let (file, ok) = match talon let (file, ok) =
.storage match talon
.get_file(vid, request.uri().path(), request.headers()) .storage
{ .get_file(subdomain, vid, request.uri().path(), request.headers())
Ok(file) => (file, true), {
Err(StorageError::NotFound(f)) => { Ok(file) => (file, true),
let version = talon.db.get_version(subdomain, vid)?; Err(StorageError::NotFound(f)) => {
if let Some(fallback) = &version.fallback { let version = talon.db.get_version(subdomain, vid)?;
( if let Some(fallback) = &version.fallback {
talon.storage.get_file(vid, fallback, request.headers())?, (
version.spa, talon
) .storage
} else if version.spa { .get_file(subdomain, vid, fallback, request.headers())?,
(talon.storage.get_file(vid, "", request.headers())?, true) version.spa,
} else { )
return Err(StorageError::NotFound(f).into()); } else if version.spa {
(
talon
.storage
.get_file(subdomain, vid, "", request.headers())?,
true,
)
} else {
return Err(StorageError::NotFound(f).into());
}
} }
} Err(e) => return Err(e.into()),
Err(e) => return Err(e.into()), };
};
Ok(match file.rd_path { Ok(match file.rd_path {
Some(rd_path) => Redirect::moved_permanent(rd_path).into_response(), Some(rd_path) => Redirect::moved_permanent(rd_path).into_response(),

View file

@ -114,6 +114,7 @@ impl Storage {
pub fn insert_file<P: AsRef<Path>>( pub fn insert_file<P: AsRef<Path>>(
&self, &self,
file_path: P, file_path: P,
subdomain: &str,
version: u32, version: u32,
site_path: &str, site_path: &str,
) -> Result<()> { ) -> Result<()> {
@ -153,7 +154,7 @@ impl Storage {
} }
} }
self.db.insert_file(version, site_path, &hash)?; self.db.insert_file(subdomain, version, site_path, &hash)?;
Ok(()) Ok(())
} }
@ -210,30 +211,40 @@ impl Storage {
} }
/// Insert a directory of files into the store /// Insert a directory of files into the store
pub fn insert_dir<P: AsRef<Path>>(&self, dir: P, version: u32) -> Result<()> { pub fn insert_dir<P: AsRef<Path>>(&self, dir: P, subdomain: &str, version: u32) -> Result<()> {
Self::visit_files(dir, "", &|file_path, site_path| { Self::visit_files(dir, "", &|file_path, site_path| {
self.insert_file(file_path, version, site_path) self.insert_file(file_path, subdomain, version, site_path)
})?; })?;
Ok(()) Ok(())
} }
/// Insert the contents of a zip archive into the store /// Insert the contents of a zip archive into the store
pub fn insert_zip_archive(&self, reader: impl Read + Seek, version: u32) -> Result<()> { pub fn insert_zip_archive(
&self,
reader: impl Read + Seek,
subdomain: &str,
version: u32,
) -> Result<()> {
let temp = TempDir::with_prefix(TMPDIR_PREFIX)?; let temp = TempDir::with_prefix(TMPDIR_PREFIX)?;
let mut zip = ZipArchive::new(reader)?; let mut zip = ZipArchive::new(reader)?;
zip.extract(temp.path())?; zip.extract(temp.path())?;
let import_path = Self::fix_archive_path(temp.path())?; let import_path = Self::fix_archive_path(temp.path())?;
self.insert_dir(import_path, version) self.insert_dir(import_path, subdomain, version)
} }
/// Insert the contents of a tar.gz archive into the store /// Insert the contents of a tar.gz archive into the store
pub fn insert_tgz_archive(&self, reader: impl Read, version: u32) -> Result<()> { pub fn insert_tgz_archive(
&self,
reader: impl Read,
subdomain: &str,
version: u32,
) -> Result<()> {
let temp = TempDir::with_prefix(TMPDIR_PREFIX)?; let temp = TempDir::with_prefix(TMPDIR_PREFIX)?;
let decoder = GzDecoder::new(reader); let decoder = GzDecoder::new(reader);
let mut archive = tar::Archive::new(decoder); let mut archive = tar::Archive::new(decoder);
archive.unpack(temp.path())?; archive.unpack(temp.path())?;
let import_path = Self::fix_archive_path(temp.path())?; let import_path = Self::fix_archive_path(temp.path())?;
self.insert_dir(import_path, version) self.insert_dir(import_path, subdomain, version)
} }
/// Get the path of a file with the given hash while creating the subdirectory /// Get the path of a file with the given hash while creating the subdirectory
@ -302,7 +313,13 @@ impl Storage {
/// Get a file using the raw site path and the website version /// Get a file using the raw site path and the website version
/// ///
/// HTTP headers are used to determine if the compressed version of a file should be returned. /// HTTP headers are used to determine if the compressed version of a file should be returned.
pub fn get_file(&self, version: u32, site_path: &str, headers: &HeaderMap) -> Result<GotFile> { pub fn get_file(
&self,
subdomain: &str,
version: u32,
site_path: &str,
headers: &HeaderMap,
) -> Result<GotFile> {
let sp = util::trim_site_path(site_path); let sp = util::trim_site_path(site_path);
let mut new_path: Cow<str> = sp.into(); let mut new_path: Cow<str> = sp.into();
let mut rd_path = None; let mut rd_path = None;
@ -315,7 +332,7 @@ impl Storage {
// Attempt to access the following pages // Attempt to access the following pages
// 1. Site path directly // 1. Site path directly
// 2. Site path + `/index.html` // 2. Site path + `/index.html`
match self.db.get_file_opt(version, sp)? { match self.db.get_file_opt(subdomain, version, sp)? {
Some(h) => { Some(h) => {
hash = Some(h); hash = Some(h);
} }
@ -334,7 +351,7 @@ impl Storage {
Some(hash) => hash, Some(hash) => hash,
None => self None => self
.db .db
.get_file_opt(version, &new_path)? .get_file_opt(subdomain, version, &new_path)?
.ok_or_else(|| StorageError::NotFound(sp.to_owned()))?, .ok_or_else(|| StorageError::NotFound(sp.to_owned()))?,
}; };

166
tests/fixtures/mod.rs vendored
View file

@ -19,12 +19,6 @@ pub const SUBDOMAIN_2: &str = "spotify-gender-ex";
pub const SUBDOMAIN_3: &str = "rustypipe"; pub const SUBDOMAIN_3: &str = "rustypipe";
pub const SUBDOMAIN_4: &str = "spa"; pub const SUBDOMAIN_4: &str = "spa";
pub const VERSION_1_1: u32 = 1;
pub const VERSION_1_2: u32 = 2;
pub const VERSION_2_1: u32 = 3;
pub const VERSION_3_1: u32 = 4;
pub const VERSION_4_1: u32 = 5;
pub const HASH_1_1_INDEX: [u8; 32] = pub const HASH_1_1_INDEX: [u8; 32] =
hex!("3b5f6bad5376897435def176d0fe77e5b9b4f0deafc7491fc27262650744ad68"); hex!("3b5f6bad5376897435def176d0fe77e5b9b4f0deafc7491fc27262650744ad68");
pub const HASH_1_1_STYLE: [u8; 32] = pub const HASH_1_1_STYLE: [u8; 32] =
@ -76,7 +70,7 @@ 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),
latest_version: Some(VERSION_1_2), latest_version: Some(2),
color: Some(2068974), color: Some(2068974),
visibility: talon::model::Visibility::Featured, visibility: talon::model::Visibility::Featured,
..Default::default() ..Default::default()
@ -88,7 +82,7 @@ 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),
latest_version: Some(VERSION_2_1), latest_version: Some(1),
color: Some(1947988), color: Some(1947988),
visibility: talon::model::Visibility::Featured, visibility: talon::model::Visibility::Featured,
source_url: Some("https://github.com/Theta-Dev/Spotify-Gender-Ex".to_owned()), source_url: Some("https://github.com/Theta-Dev/Spotify-Gender-Ex".to_owned()),
@ -102,7 +96,7 @@ 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),
latest_version: Some(VERSION_3_1), latest_version: Some(1),
color: Some(7943647), color: Some(7943647),
visibility: talon::model::Visibility::Featured, visibility: talon::model::Visibility::Featured,
source_url: Some("https://code.thetadev.de/ThetaDev/rustypipe".to_owned()), source_url: Some("https://code.thetadev.de/ThetaDev/rustypipe".to_owned()),
@ -116,7 +110,7 @@ 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),
latest_version: Some(VERSION_4_1), latest_version: Some(1),
color: Some(16727552), color: Some(16727552),
visibility: talon::model::Visibility::Hidden, visibility: talon::model::Visibility::Hidden,
..Default::default() ..Default::default()
@ -130,17 +124,18 @@ fn insert_websites(db: &Db) {
"Deployed by".to_owned(), "Deployed by".to_owned(),
"https://github.com/Theta-Dev/Talon/actions/runs/1352014628".to_owned(), "https://github.com/Theta-Dev/Talon/actions/runs/1352014628".to_owned(),
); );
assert_eq!(db.new_version_id().unwrap(), VERSION_1_1); assert_eq!(
db.insert_version( db.insert_version(
SUBDOMAIN_1, SUBDOMAIN_1,
VERSION_1_1, &Version {
&Version { created_at: datetime!(2023-02-18 16:30 +0),
created_at: datetime!(2023-02-18 16:30 +0), data: v1_data,
data: v1_data, ..Default::default()
..Default::default() },
}, )
) .unwrap(),
.unwrap(); 1
);
let mut v2_data = BTreeMap::new(); let mut v2_data = BTreeMap::new();
v2_data.insert("Version".to_owned(), "v0.1.1".to_owned()); v2_data.insert("Version".to_owned(), "v0.1.1".to_owned());
@ -148,51 +143,54 @@ fn insert_websites(db: &Db) {
"Deployed by".to_owned(), "Deployed by".to_owned(),
"https://github.com/Theta-Dev/Talon/actions/runs/1354755231".to_owned(), "https://github.com/Theta-Dev/Talon/actions/runs/1354755231".to_owned(),
); );
assert_eq!(db.new_version_id().unwrap(), VERSION_1_2); assert_eq!(
db.insert_version( db.insert_version(
SUBDOMAIN_1, SUBDOMAIN_1,
VERSION_1_2, &Version {
&Version { created_at: datetime!(2023-02-18 16:52 +0),
created_at: datetime!(2023-02-18 16:52 +0), data: v2_data,
data: v2_data, ..Default::default()
..Default::default() },
}, )
) .unwrap(),
.unwrap(); 2
);
assert_eq!(db.new_version_id().unwrap(), VERSION_2_1); assert_eq!(
db.insert_version( db.insert_version(
SUBDOMAIN_2, SUBDOMAIN_2,
VERSION_2_1, &Version {
&Version { created_at: datetime!(2023-02-18 16:30 +0),
created_at: datetime!(2023-02-18 16:30 +0), ..Default::default()
..Default::default() },
}, )
) .unwrap(),
.unwrap(); 1
assert_eq!(db.new_version_id().unwrap(), VERSION_3_1); );
db.insert_version( assert_eq!(
SUBDOMAIN_3, db.insert_version(
VERSION_3_1, SUBDOMAIN_3,
&Version { &Version {
created_at: datetime!(2023-02-20 18:30 +0), created_at: datetime!(2023-02-20 18:30 +0),
..Default::default() ..Default::default()
}, },
) )
.unwrap(); .unwrap(),
1
assert_eq!(db.new_version_id().unwrap(), VERSION_4_1); );
db.insert_version( assert_eq!(
SUBDOMAIN_4, db.insert_version(
VERSION_4_1, SUBDOMAIN_4,
&Version { &Version {
created_at: datetime!(2023-03-03 22:00 +0), created_at: datetime!(2023-03-03 22:00 +0),
fallback: Some("200.html".to_owned()), fallback: Some("200.html".to_owned()),
spa: true, spa: true,
..Default::default() ..Default::default()
}, },
) )
.unwrap(); .unwrap(),
1
);
} }
#[fixture] #[fixture]
@ -201,44 +199,47 @@ pub fn db() -> DbTest {
let db = Db::new(&temp).unwrap(); let db = Db::new(&temp).unwrap();
insert_websites(&db); insert_websites(&db);
db.insert_file(VERSION_1_1, "index.html", &HASH_1_1_INDEX) db.insert_file(SUBDOMAIN_1, 1, "index.html", &HASH_1_1_INDEX)
.unwrap(); .unwrap();
db.insert_file(VERSION_1_1, "style.css", &HASH_1_1_STYLE) db.insert_file(SUBDOMAIN_1, 1, "style.css", &HASH_1_1_STYLE)
.unwrap(); .unwrap();
db.insert_file(VERSION_1_2, "index.html", &HASH_1_2_INDEX) db.insert_file(SUBDOMAIN_1, 2, "index.html", &HASH_1_2_INDEX)
.unwrap(); .unwrap();
db.insert_file(VERSION_1_2, "assets/style.css", &HASH_1_2_STYLE) db.insert_file(SUBDOMAIN_1, 2, "assets/style.css", &HASH_1_2_STYLE)
.unwrap(); .unwrap();
db.insert_file( db.insert_file(
VERSION_1_2, SUBDOMAIN_1,
2,
"assets/image.jpg", "assets/image.jpg",
&hex!("901d291a47a8a9b55c06f84e5e5f82fd2dcee65cac1406d6e878b805d45c1e93"), &hex!("901d291a47a8a9b55c06f84e5e5f82fd2dcee65cac1406d6e878b805d45c1e93"),
) )
.unwrap(); .unwrap();
db.insert_file( db.insert_file(
VERSION_1_2, SUBDOMAIN_1,
2,
"assets/test.js", "assets/test.js",
&hex!("b6ed35f5ae339a35a8babb11a91ff90c1a62ef250d30fa98e59500e8dbb896fa"), &hex!("b6ed35f5ae339a35a8babb11a91ff90c1a62ef250d30fa98e59500e8dbb896fa"),
) )
.unwrap(); .unwrap();
db.insert_file( db.insert_file(
VERSION_1_2, SUBDOMAIN_1,
2,
"data/example.txt", "data/example.txt",
&hex!("bae6bdae8097c24f9a99028e04bfc8d5e0a0c318955316db0e7b955def9c1dbb"), &hex!("bae6bdae8097c24f9a99028e04bfc8d5e0a0c318955316db0e7b955def9c1dbb"),
) )
.unwrap(); .unwrap();
db.insert_file(VERSION_2_1, "index.html", &HASH_2_1_INDEX) db.insert_file(SUBDOMAIN_2, 1, "index.html", &HASH_2_1_INDEX)
.unwrap(); .unwrap();
db.insert_file(VERSION_2_1, "gex_style.css", &HASH_2_1_STYLE) db.insert_file(SUBDOMAIN_2, 1, "gex_style.css", &HASH_2_1_STYLE)
.unwrap(); .unwrap();
db.insert_file(VERSION_3_1, "index.html", &HASH_3_1_INDEX) db.insert_file(SUBDOMAIN_3, 1, "index.html", &HASH_3_1_INDEX)
.unwrap(); .unwrap();
db.insert_file(VERSION_3_1, "rp_style.css", &HASH_3_1_STYLE) db.insert_file(SUBDOMAIN_3, 1, "rp_style.css", &HASH_3_1_STYLE)
.unwrap(); .unwrap();
db.insert_file(VERSION_3_1, "page2/index.html", &HASH_3_1_PAGE2) db.insert_file(SUBDOMAIN_3, 1, "page2/index.html", &HASH_3_1_PAGE2)
.unwrap(); .unwrap();
DbTest { db, _temp: temp } DbTest { db, _temp: temp }
@ -266,25 +267,26 @@ pub fn tln() -> TalonTest {
talon talon
.storage .storage
.insert_dir(path!("tests" / "testfiles" / "ThetaDev0"), VERSION_1_1) .insert_dir(path!("tests" / "testfiles" / "ThetaDev0"), SUBDOMAIN_1, 1)
.unwrap(); .unwrap();
talon talon
.storage .storage
.insert_dir(path!("tests" / "testfiles" / "ThetaDev1"), VERSION_1_2) .insert_dir(path!("tests" / "testfiles" / "ThetaDev1"), SUBDOMAIN_1, 2)
.unwrap(); .unwrap();
talon talon
.storage .storage
.insert_dir(path!("tests" / "testfiles" / "GenderEx"), VERSION_2_1) .insert_dir(path!("tests" / "testfiles" / "GenderEx"), SUBDOMAIN_2, 1)
.unwrap(); .unwrap();
talon talon
.storage .storage
.insert_dir(path!("tests" / "testfiles" / "RustyPipe"), VERSION_3_1) .insert_dir(path!("tests" / "testfiles" / "RustyPipe"), SUBDOMAIN_3, 1)
.unwrap(); .unwrap();
talon talon
.storage .storage
.insert_tgz_archive( .insert_tgz_archive(
File::open(path!("tests" / "testfiles" / "archive" / "spa.tar.gz")).unwrap(), File::open(path!("tests" / "testfiles" / "archive" / "spa.tar.gz")).unwrap(),
VERSION_4_1, SUBDOMAIN_4,
1,
) )
.unwrap(); .unwrap();

View file

@ -2,14 +2,14 @@
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":4,"color":7943647,"visibility":"featured","source_url":"https://code.thetadev.de/ThetaDev/rustypipe","source_icon":"gitea"}} {"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}}
{"type":"website","key":"spa","value":{"name":"SvelteKit SPA","created_at":[2023,62,22,0,0,0,0,0,0],"latest_version":5,"color":16727552,"visibility":"hidden","source_url":null,"source_icon":null}} {"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}}
{"type":"website","key":"spotify-gender-ex","value":{"name":"Spotify-Gender-Ex","created_at":[2023,49,16,30,0,0,0,0,0],"latest_version":3,"color":1947988,"visibility":"featured","source_url":"https://github.com/Theta-Dev/Spotify-Gender-Ex","source_icon":"github"}} {"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}}
{"type":"version","key":"rustypipe:4","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:5","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:3","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}}
{"type":"file","key":"3:gex_style.css","value":"fc825b409a49724af8f5b3c4ad15e175e68095ea746237a7b46152d3f383f541"} {"type":"file","key":"rustypipe:1:index.html","value":"cc31423924cf1f124750825861ab1ccc675e755921fc2fa111c0a98e8c346a5e"}
{"type":"file","key":"3:index.html","value":"6c5d37546616519e8973be51515b8a90898b4675f7b6d01f2d891edb686408a2"} {"type":"file","key":"rustypipe:1:page2/index.html","value":"be4f409ca0adcb21cdc7130cde63031718406726f889ef97ac8870c90b330a75"}
{"type":"file","key":"4:index.html","value":"cc31423924cf1f124750825861ab1ccc675e755921fc2fa111c0a98e8c346a5e"} {"type":"file","key":"rustypipe:1:rp_style.css","value":"ee4fc4911a56e627c047a29ba3085131939d8d487759b9149d42aaab89ce8993"}
{"type":"file","key":"4:page2/index.html","value":"be4f409ca0adcb21cdc7130cde63031718406726f889ef97ac8870c90b330a75"} {"type":"file","key":"spotify-gender-ex:1:gex_style.css","value":"fc825b409a49724af8f5b3c4ad15e175e68095ea746237a7b46152d3f383f541"}
{"type":"file","key":"4:rp_style.css","value":"ee4fc4911a56e627c047a29ba3085131939d8d487759b9149d42aaab89ce8993"} {"type":"file","key":"spotify-gender-ex:1:index.html","value":"6c5d37546616519e8973be51515b8a90898b4675f7b6d01f2d891edb686408a2"}

View file

@ -2,24 +2,24 @@
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}} {"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}}
{"type":"website","key":"rustypipe","value":{"name":"RustyPipe","created_at":[2023,51,18,30,0,0,0,0,0],"latest_version":4,"color":7943647,"visibility":"featured","source_url":"https://code.thetadev.de/ThetaDev/rustypipe","source_icon":"gitea"}} {"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}}
{"type":"website","key":"spa","value":{"name":"SvelteKit SPA","created_at":[2023,62,22,0,0,0,0,0,0],"latest_version":5,"color":16727552,"visibility":"hidden","source_url":null,"source_icon":null}} {"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}}
{"type":"website","key":"spotify-gender-ex","value":{"name":"Spotify-Gender-Ex","created_at":[2023,49,16,30,0,0,0,0,0],"latest_version":3,"color":1947988,"visibility":"featured","source_url":"https://github.com/Theta-Dev/Spotify-Gender-Ex","source_icon":"github"}} {"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}}
{"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:4","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:5","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:3","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}}
{"type":"file","key":"1:index.html","value":"3b5f6bad5376897435def176d0fe77e5b9b4f0deafc7491fc27262650744ad68"} {"type":"file","key":"-:1:index.html","value":"3b5f6bad5376897435def176d0fe77e5b9b4f0deafc7491fc27262650744ad68"}
{"type":"file","key":"1:style.css","value":"356f131c825fbf604797c7e9c85352549d81db8af91fee834016d075110af026"} {"type":"file","key":"-:1:style.css","value":"356f131c825fbf604797c7e9c85352549d81db8af91fee834016d075110af026"}
{"type":"file","key":"2:assets/image.jpg","value":"901d291a47a8a9b55c06f84e5e5f82fd2dcee65cac1406d6e878b805d45c1e93"} {"type":"file","key":"-:2:assets/image.jpg","value":"901d291a47a8a9b55c06f84e5e5f82fd2dcee65cac1406d6e878b805d45c1e93"}
{"type":"file","key":"2:assets/style.css","value":"356f131c825fbf604797c7e9c85352549d81db8af91fee834016d075110af026"} {"type":"file","key":"-:2:assets/style.css","value":"356f131c825fbf604797c7e9c85352549d81db8af91fee834016d075110af026"}
{"type":"file","key":"2:assets/test.js","value":"b6ed35f5ae339a35a8babb11a91ff90c1a62ef250d30fa98e59500e8dbb896fa"} {"type":"file","key":"-:2:assets/test.js","value":"b6ed35f5ae339a35a8babb11a91ff90c1a62ef250d30fa98e59500e8dbb896fa"}
{"type":"file","key":"2:data/example.txt","value":"bae6bdae8097c24f9a99028e04bfc8d5e0a0c318955316db0e7b955def9c1dbb"} {"type":"file","key":"-:2:data/example.txt","value":"bae6bdae8097c24f9a99028e04bfc8d5e0a0c318955316db0e7b955def9c1dbb"}
{"type":"file","key":"2:index.html","value":"a44816e6c3b650bdf88e6532659ba07ef187c2113ae311da9709e056aec8eadb"} {"type":"file","key":"-:2:index.html","value":"a44816e6c3b650bdf88e6532659ba07ef187c2113ae311da9709e056aec8eadb"}
{"type":"file","key":"3:gex_style.css","value":"fc825b409a49724af8f5b3c4ad15e175e68095ea746237a7b46152d3f383f541"} {"type":"file","key":"rustypipe:1:index.html","value":"cc31423924cf1f124750825861ab1ccc675e755921fc2fa111c0a98e8c346a5e"}
{"type":"file","key":"3:index.html","value":"6c5d37546616519e8973be51515b8a90898b4675f7b6d01f2d891edb686408a2"} {"type":"file","key":"rustypipe:1:page2/index.html","value":"be4f409ca0adcb21cdc7130cde63031718406726f889ef97ac8870c90b330a75"}
{"type":"file","key":"4:index.html","value":"cc31423924cf1f124750825861ab1ccc675e755921fc2fa111c0a98e8c346a5e"} {"type":"file","key":"rustypipe:1:rp_style.css","value":"ee4fc4911a56e627c047a29ba3085131939d8d487759b9149d42aaab89ce8993"}
{"type":"file","key":"4:page2/index.html","value":"be4f409ca0adcb21cdc7130cde63031718406726f889ef97ac8870c90b330a75"} {"type":"file","key":"spotify-gender-ex:1:gex_style.css","value":"fc825b409a49724af8f5b3c4ad15e175e68095ea746237a7b46152d3f383f541"}
{"type":"file","key":"4:rp_style.css","value":"ee4fc4911a56e627c047a29ba3085131939d8d487759b9149d42aaab89ce8993"} {"type":"file","key":"spotify-gender-ex:1:index.html","value":"6c5d37546616519e8973be51515b8a90898b4675f7b6d01f2d891edb686408a2"}

View file

@ -11,23 +11,26 @@ expression: "vec![ws1, ws2, ws3]"
visibility: featured, visibility: featured,
source_url: None, source_url: None,
source_icon: None, source_icon: None,
vid_count: 2,
), ),
Website( Website(
name: "Spotify-Gender-Ex", name: "Spotify-Gender-Ex",
created_at: (2023, 49, 16, 30, 0, 0, 0, 0, 0), created_at: (2023, 49, 16, 30, 0, 0, 0, 0, 0),
latest_version: Some(3), latest_version: Some(1),
color: Some(1947988), color: Some(1947988),
visibility: featured, visibility: featured,
source_url: Some("https://github.com/Theta-Dev/Spotify-Gender-Ex"), source_url: Some("https://github.com/Theta-Dev/Spotify-Gender-Ex"),
source_icon: Some(github), source_icon: Some(github),
vid_count: 1,
), ),
Website( Website(
name: "RustyPipe", name: "RustyPipe",
created_at: (2023, 51, 18, 30, 0, 0, 0, 0, 0), created_at: (2023, 51, 18, 30, 0, 0, 0, 0, 0),
latest_version: Some(4), latest_version: Some(1),
color: Some(7943647), color: Some(7943647),
visibility: featured, visibility: featured,
source_url: Some("https://code.thetadev.de/ThetaDev/rustypipe"), source_url: Some("https://code.thetadev.de/ThetaDev/rustypipe"),
source_icon: Some(gitea), source_icon: Some(gitea),
vid_count: 1,
), ),
] ]

View file

@ -11,32 +11,36 @@ expression: websites
visibility: featured, visibility: featured,
source_url: None, source_url: None,
source_icon: None, source_icon: None,
vid_count: 2,
)), )),
("rustypipe", Website( ("rustypipe", Website(
name: "RustyPipe", name: "RustyPipe",
created_at: (2023, 51, 18, 30, 0, 0, 0, 0, 0), created_at: (2023, 51, 18, 30, 0, 0, 0, 0, 0),
latest_version: Some(4), latest_version: Some(1),
color: Some(7943647), color: Some(7943647),
visibility: featured, visibility: featured,
source_url: Some("https://code.thetadev.de/ThetaDev/rustypipe"), source_url: Some("https://code.thetadev.de/ThetaDev/rustypipe"),
source_icon: Some(gitea), source_icon: Some(gitea),
vid_count: 1,
)), )),
("spa", Website( ("spa", Website(
name: "SvelteKit SPA", name: "SvelteKit SPA",
created_at: (2023, 62, 22, 0, 0, 0, 0, 0, 0), created_at: (2023, 62, 22, 0, 0, 0, 0, 0, 0),
latest_version: Some(5), latest_version: Some(1),
color: Some(16727552), color: Some(16727552),
visibility: hidden, visibility: hidden,
source_url: None, source_url: None,
source_icon: None, source_icon: None,
vid_count: 1,
)), )),
("spotify-gender-ex", Website( ("spotify-gender-ex", Website(
name: "Spotify-Gender-Ex", name: "Spotify-Gender-Ex",
created_at: (2023, 49, 16, 30, 0, 0, 0, 0, 0), created_at: (2023, 49, 16, 30, 0, 0, 0, 0, 0),
latest_version: Some(3), latest_version: Some(1),
color: Some(1947988), color: Some(1947988),
visibility: featured, visibility: featured,
source_url: Some("https://github.com/Theta-Dev/Spotify-Gender-Ex"), source_url: Some("https://github.com/Theta-Dev/Spotify-Gender-Ex"),
source_icon: Some(github), source_icon: Some(github),
vid_count: 1,
)), )),
] ]

View file

@ -10,4 +10,5 @@ Website(
visibility: hidden, visibility: hidden,
source_url: Some("https://example.com"), source_url: Some("https://example.com"),
source_icon: Some(link), source_icon: Some(link),
vid_count: 2,
) )

View file

@ -94,30 +94,29 @@ mod database {
#[rstest] #[rstest]
fn get_version(db: DbTest) { fn get_version(db: DbTest) {
let version = db.get_version(SUBDOMAIN_1, VERSION_1_1).unwrap(); let version = db.get_version(SUBDOMAIN_1, 1).unwrap();
insta::assert_ron_snapshot!(version); insta::assert_ron_snapshot!(version);
} }
#[rstest] #[rstest]
fn delete_version(db: DbTest) { fn delete_version(db: DbTest) {
db.delete_version(SUBDOMAIN_1, VERSION_1_2, true).unwrap(); db.delete_version(SUBDOMAIN_1, 2, true).unwrap();
assert!(matches!( assert!(matches!(
db.get_version(SUBDOMAIN_1, VERSION_1_2).unwrap_err(), db.get_version(SUBDOMAIN_1, 2).unwrap_err(),
DbError::NotExists(_, _) DbError::NotExists(_, _)
)); ));
assert!(matches!( assert!(matches!(
db.delete_version(SUBDOMAIN_1, VERSION_1_2, true) db.delete_version(SUBDOMAIN_1, 2, true).unwrap_err(),
.unwrap_err(),
DbError::NotExists(_, _) DbError::NotExists(_, _)
)); ));
db.delete_version(SUBDOMAIN_1, VERSION_1_2, false).unwrap(); db.delete_version(SUBDOMAIN_1, 2, false).unwrap();
// Check if files were deleted // Check if files were deleted
assert!(db.get_version_files(VERSION_1_2).next().is_none()); assert!(db.get_version_files(SUBDOMAIN_1, 2).next().is_none());
// Check if latest version was updated // Check if latest version was updated
let ws = db.get_website(SUBDOMAIN_1).unwrap(); let ws = db.get_website(SUBDOMAIN_1).unwrap();
assert_eq!(ws.latest_version, Some(VERSION_1_1)); assert_eq!(ws.latest_version, Some(1));
} }
#[rstest] #[rstest]
@ -135,33 +134,34 @@ mod database {
.get_website_version_ids(SUBDOMAIN_1) .get_website_version_ids(SUBDOMAIN_1)
.map(|v| v.unwrap()) .map(|v| v.unwrap())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!(ids, vec![VERSION_1_1, VERSION_1_2]); assert_eq!(ids, vec![1, 2]);
} }
#[rstest] #[rstest]
fn get_file(db: DbTest) { fn get_file(db: DbTest) {
let hash = db.get_file(VERSION_1_1, "index.html").unwrap(); let hash = db.get_file(SUBDOMAIN_1, 1, "index.html").unwrap();
assert_eq!(hash, HASH_1_1_INDEX); assert_eq!(hash, HASH_1_1_INDEX);
} }
#[rstest] #[rstest]
fn delete_file(db: DbTest) { fn delete_file(db: DbTest) {
db.delete_file(VERSION_1_1, "index.html", true).unwrap(); db.delete_file(SUBDOMAIN_1, 1, "index.html", true).unwrap();
assert!(matches!( assert!(matches!(
db.get_file(VERSION_1_1, "index.html").unwrap_err(), db.get_file(SUBDOMAIN_1, 1, "index.html").unwrap_err(),
DbError::NotExists(_, _) DbError::NotExists(_, _)
)); ));
assert!(matches!( assert!(matches!(
db.delete_file(VERSION_1_1, "index.html", true).unwrap_err(), db.delete_file(SUBDOMAIN_1, 1, "index.html", true)
.unwrap_err(),
DbError::NotExists(_, _) DbError::NotExists(_, _)
)); ));
db.delete_file(VERSION_1_1, "index.html", false).unwrap(); db.delete_file(SUBDOMAIN_1, 1, "index.html", false).unwrap();
} }
#[rstest] #[rstest]
fn get_version_files(db: DbTest) { fn get_version_files(db: DbTest) {
let files = db let files = db
.get_version_files(VERSION_1_1) .get_version_files(SUBDOMAIN_1, 1)
.map(|f| f.unwrap()) .map(|f| f.unwrap())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!( assert_eq!(
@ -201,10 +201,10 @@ mod storage {
let temp = temp_testdir::TempDir::default(); let temp = temp_testdir::TempDir::default();
let store = Storage::new(temp.to_path_buf(), db_empty.clone(), Default::default()); let store = Storage::new(temp.to_path_buf(), db_empty.clone(), Default::default());
store.insert_dir(dir, 1).unwrap(); store.insert_dir(dir, SUBDOMAIN_1, 1).unwrap();
let files = db_empty let files = db_empty
.get_version_files(1) .get_version_files(SUBDOMAIN_1, 1)
.map(|f| f.unwrap()) .map(|f| f.unwrap())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
insta::assert_ron_snapshot!("insert_files", files); insta::assert_ron_snapshot!("insert_files", files);
@ -223,11 +223,11 @@ mod storage {
let store = Storage::new(temp.to_path_buf(), db_empty.clone(), Default::default()); let store = Storage::new(temp.to_path_buf(), db_empty.clone(), Default::default());
store store
.insert_zip_archive(File::open(archive).unwrap(), 1) .insert_zip_archive(File::open(archive).unwrap(), SUBDOMAIN_1, 1)
.unwrap(); .unwrap();
let files = db_empty let files = db_empty
.get_version_files(1) .get_version_files(SUBDOMAIN_1, 1)
.map(|f| f.unwrap()) .map(|f| f.unwrap())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
insta::assert_ron_snapshot!("insert_files", files); insta::assert_ron_snapshot!("insert_files", files);
@ -246,11 +246,11 @@ mod storage {
let store = Storage::new(temp.to_path_buf(), db_empty.clone(), Default::default()); let store = Storage::new(temp.to_path_buf(), db_empty.clone(), Default::default());
store store
.insert_tgz_archive(File::open(archive).unwrap(), 1) .insert_tgz_archive(File::open(archive).unwrap(), SUBDOMAIN_1, 1)
.unwrap(); .unwrap();
let files = db_empty let files = db_empty
.get_version_files(1) .get_version_files(SUBDOMAIN_1, 1)
.map(|f| f.unwrap()) .map(|f| f.unwrap())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
insta::assert_ron_snapshot!("insert_files", files); insta::assert_ron_snapshot!("insert_files", files);
@ -278,9 +278,9 @@ mod storage {
}); });
let store = Storage::new(temp.to_path_buf(), db_empty.clone(), cfg); let store = Storage::new(temp.to_path_buf(), db_empty.clone(), cfg);
store.insert_dir(dir, 1).unwrap(); store.insert_dir(dir, SUBDOMAIN_1, 1).unwrap();
for f in db_empty.get_version_files(1) { for f in db_empty.get_version_files(SUBDOMAIN_1, 1) {
let hash = f.unwrap().1; let hash = f.unwrap().1;
let hash_str = hash.encode_hex::<String>(); let hash_str = hash.encode_hex::<String>();
let path = temp.join(&hash_str[..2]).join(&hash_str); let path = temp.join(&hash_str[..2]).join(&hash_str);
@ -297,15 +297,16 @@ mod storage {
} }
#[rstest] #[rstest]
#[case::index("br", VERSION_1_2, "", false, "text/html", None)] #[case::index("br", SUBDOMAIN_1, 2, "", false, "text/html", None)]
#[case::nocmp("", VERSION_1_2, "assets/style.css", true, "text/css", None)] #[case::nocmp("", SUBDOMAIN_1, 2, "assets/style.css", true, "text/css", None)]
#[case::gzip("gzip", VERSION_1_2, "assets/style.css", true, "text/css", None)] #[case::gzip("gzip", SUBDOMAIN_1, 2, "assets/style.css", true, "text/css", None)]
#[case::br("br", VERSION_1_2, "assets/style.css", true, "text/css", None)] #[case::br("br", SUBDOMAIN_1, 2, "assets/style.css", true, "text/css", None)]
#[case::image("br", VERSION_1_2, "assets/image.jpg", false, "image/jpeg", None)] #[case::image("br", SUBDOMAIN_1, 2, "assets/image.jpg", false, "image/jpeg", None)]
#[case::subdir("br", VERSION_3_1, "page2", false, "text/html", Some("/page2/"))] #[case::subdir("br", SUBDOMAIN_3, 1, "page2", false, "text/html", Some("/page2/"))]
fn get_file( fn get_file(
tln: TalonTest, tln: TalonTest,
#[case] encoding: &str, #[case] encoding: &str,
#[case] subdomain: &str,
#[case] version: u32, #[case] version: u32,
#[case] path: &str, #[case] path: &str,
#[case] compressible: bool, #[case] compressible: bool,
@ -325,7 +326,10 @@ mod storage {
None None
}; };
let index_file = tln.storage.get_file(version, path, &headers).unwrap(); let index_file = tln
.storage
.get_file(subdomain, version, path, &headers)
.unwrap();
dbg!(&index_file); dbg!(&index_file);
assert!(index_file.file_path.is_file()); assert!(index_file.file_path.is_file());
assert_eq!( assert_eq!(
@ -620,38 +624,43 @@ mod page {
#[case] ok: bool, #[case] ok: bool,
#[case] hash: &[u8], #[case] hash: &[u8],
) { ) {
let vid = tln.db.new_version_id().unwrap(); const SUBDOMAIN: &str = "fallback";
tln.db tln.db
.insert_website( .insert_website(
"fallback", SUBDOMAIN,
&talon::db::model::Website { &talon::db::model::Website {
latest_version: Some(vid), latest_version: Some(1),
..Default::default()
},
)
.unwrap();
tln.db
.insert_version(
"fallback",
vid,
&talon::db::model::Version {
fallback,
spa,
..Default::default() ..Default::default()
}, },
) )
.unwrap(); .unwrap();
assert_eq!(
tln.db
.insert_version(
SUBDOMAIN,
&talon::db::model::Version {
fallback,
spa,
..Default::default()
},
)
.unwrap(),
1
);
tln.storage tln.storage
.insert_file( .insert_file(
path!("tests" / "testfiles" / "ThetaDev0" / "index.html"), path!("tests" / "testfiles" / "ThetaDev0" / "index.html"),
vid, SUBDOMAIN,
1,
"index.html", "index.html",
) )
.unwrap(); .unwrap();
tln.storage tln.storage
.insert_file( .insert_file(
path!("tests" / "testfiles" / "ThetaDev1" / "index.html"), path!("tests" / "testfiles" / "ThetaDev1" / "index.html"),
vid, SUBDOMAIN,
1,
"fallback.html", "fallback.html",
) )
.unwrap(); .unwrap();

6787
ui/menu/package-lock.json generated

File diff suppressed because it is too large Load diff