Skip to content

Commit

Permalink
feat: Check and store package dependencies in database
Browse files Browse the repository at this point in the history
  • Loading branch information
sdankel committed Jan 29, 2025
1 parent efd3abe commit 34c0fe9
Show file tree
Hide file tree
Showing 23 changed files with 1,294 additions and 155 deletions.
1,020 changes: 957 additions & 63 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@ serial_test = "3.1.1"
pinata-sdk = "1.1.0"
tar = "0.4.41"
flate2 = "1.0.33"
forc-util = "0.66.5"
semver = "1.0.23"
tracing = "0.1.41"
tracing-subscriber = "0.3.19"
tempfile = "3.14.0"
chrono = { version = "0.4.39", features = ["serde"] }
url = { version = "2.5.4", features = ["serde"] }
forc-util = "0.66.6"
forc-pkg = { git = "https://github.com/FuelLabs/sway", rev = "d602a0c6bd8d520ffc0f0f4a52f9284f79790eac"}
toml = "0.8.19"
serde_ignored = "0.1.10"
3 changes: 3 additions & 0 deletions Rocket.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
address = "0.0.0.0"
port = 8080
workers = 16

[default.limits]
file = "10 MB" # Set file upload limit to 10 MB
7 changes: 7 additions & 0 deletions docs/database.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,18 @@ The environment variables for connecting to the local database are set in `.env`

1. **Generate a migration:**
Run the following Diesel CLI command to create a new migration:

```bash
diesel migration generate create_users
```

This creates two files under `migrations/<timestamp>_create_users/`:

- `up.sql`: Commands to apply the migration
- `down.sql`: Commands to undo the migration

2. **Define the table in the `up.sql` file:**

```sql
CREATE TABLE users (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
Expand All @@ -48,6 +52,7 @@ The environment variables for connecting to the local database are set in `.env`
```

3. **Define the rollback in the `down.sql` file:**

```sql
DROP TABLE users;
```
Expand All @@ -63,6 +68,7 @@ The environment variables for connecting to the local database are set in `.env`
Diesel generates a Rust representation of your database schema in `src/schema.rs`.

The file will contain Rust definitions for your tables, like this:

```rust
diesel::table! {
users (id) {
Expand All @@ -88,6 +94,7 @@ For easier database management and visualization, you can use a database client
Download and install DBeaver from [dbeaver.io](https://dbeaver.io/).

2. **Connect to your database:**

- Open DBeaver and create a new database connection.
- Select your database type (e.g., PostgreSQL).
- Provide your database URL, username, and password (as specified in the `.env` file).
Expand Down
15 changes: 15 additions & 0 deletions migrations/2025-01-28-173445_move_readme_license_cols/down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- Add `readme` column back to the `package_versions` table
ALTER TABLE package_versions
ADD COLUMN readme VARCHAR;

-- Migrate data back from `uploads` to `package_versions`
UPDATE package_versions
SET
readme = u.readme
FROM uploads u
WHERE package_versions.upload_id = u.id;

-- Remove `readme` and `forc_manifest` columns from `uploads`
ALTER TABLE uploads
DROP COLUMN readme,
DROP COLUMN forc_manifest;
15 changes: 15 additions & 0 deletions migrations/2025-01-28-173445_move_readme_license_cols/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- Add `readme` and `forc_manifest` columns to the `uploads` table
ALTER TABLE uploads
ADD COLUMN readme VARCHAR,
ADD COLUMN forc_manifest VARCHAR;

-- Migrate data from `package_versions` to `uploads`
UPDATE uploads
SET
readme = pv.readme
FROM package_versions pv
WHERE uploads.id = pv.upload_id;

-- Remove `readme` column from `package_versions`
ALTER TABLE package_versions
DROP COLUMN readme;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE package_dependencies;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
CREATE TABLE package_dependencies (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
dependent_package_version_id UUID NOT NULL,
dependency_package_name VARCHAR NOT NULL,
dependency_version_req VARCHAR NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,

FOREIGN KEY (dependent_package_version_id) REFERENCES package_versions(id) ON DELETE CASCADE
);
8 changes: 6 additions & 2 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@ pub enum ApiError {
#[error("GitHub error: {0}")]
Github(#[from] crate::github::GithubError),

#[error("GitHub error: {0}")]
Upload(#[from] crate::upload::UploadError),
#[error("Upload error: {0}")]
Upload(#[from] crate::handlers::upload::UploadError),

#[error("Publish error: {0}")]
Publish(#[from] crate::handlers::publish::PublishError),
}

impl<'r, 'o: 'r> Responder<'r, 'o> for ApiError {
Expand All @@ -44,6 +47,7 @@ impl<'r, 'o: 'r> Responder<'r, 'o> for ApiError {
),
ApiError::Github(ref err) => (Status::Unauthorized, format!("GitHub error: {}", err)),
ApiError::Upload(ref err) => (Status::BadRequest, format!("Upload error: {}", err)),
ApiError::Publish(ref err) => (Status::BadRequest, format!("Publish error: {}", err)),
};
let body = json!({
"status": status.code,
Expand Down
52 changes: 1 addition & 51 deletions src/api/publish.rs
Original file line number Diff line number Diff line change
@@ -1,62 +1,12 @@
use regex::Regex;
use rocket::serde::{Deserialize, Serialize};
use serde::Deserializer;
use url::Url;
use uuid::Uuid;

fn is_valid_package_name(name: &str) -> bool {
// Must start with a letter, can contain letters, numbers, underscores and hyphens
let re = Regex::new(r"^[a-zA-Z][\w-]*$").unwrap();
re.is_match(name)
}

fn is_valid_package_version(name: &str) -> bool {
// Must start with an alphanumeric character, can contain only letters, numbers, underscores, dots and hyphens
let re = Regex::new(r"^[a-zA-Z0-9][\w.-]*$").unwrap();
re.is_match(name)
}

/// The publish request.
#[derive(Deserialize, Debug)]
pub struct PublishRequest {
#[serde(deserialize_with = "validate_package_name")]
pub package_name: String,
pub upload_id: Uuid,
#[serde(deserialize_with = "validate_package_version")]
pub num: String,
pub package_description: Option<String>,
pub repository: Option<Url>,
pub documentation: Option<Url>,
pub homepage: Option<Url>,
pub urls: Vec<Url>,
pub readme: Option<String>,
pub license: Option<String>,
}

fn validate_package_name<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: Deserializer<'de>,
{
let name = String::deserialize(deserializer)?;
if !is_valid_package_name(&name) {
return Err(serde::de::Error::custom(
"Package name must start with a letter and contain only letters, numbers, underscores or hyphens"
));
}
Ok(name)
}

fn validate_package_version<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: Deserializer<'de>,
{
let name = String::deserialize(deserializer)?;
if !is_valid_package_version(&name) {
return Err(serde::de::Error::custom(
"Package version must start with an alphanumeric character, can contain only letters, numbers, underscores, dots and hyphens",
));
}
Ok(name)
pub urls: Option<Vec<Url>>,
}

/// The response to an upload_project request.
Expand Down
3 changes: 3 additions & 0 deletions src/db/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ pub enum DatabaseError {
#[error("Failed to save package version: {0} {1}: {2}")]
InsertPackageVersionFailed(String, String, diesel::result::Error),

#[error("Failed to save package dependencies: {0}")]
InsertPackageDepFailed(diesel::result::Error),

#[error("Failed to query: {0}: {1}")]
QueryFailed(String, diesel::result::Error),
}
1 change: 1 addition & 0 deletions src/db/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod api_token;
pub mod error;
pub mod package_dependency;
pub mod package_version;
pub mod upload;
mod user_session;
Expand Down
32 changes: 32 additions & 0 deletions src/db/package_dependency.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
use super::error::DatabaseError;
use super::{models, schema, DbConn};
use crate::models::NewPackageDep;
use diesel::prelude::*;
use uuid::Uuid;

impl DbConn {
/// Insert package dependencies into the database and return the number of rows inserted.
pub fn insert_dependencies(
&mut self,
new_dependencies: Vec<NewPackageDep>,
) -> Result<usize, DatabaseError> {
diesel::insert_into(schema::package_dependencies::table)
.values(&new_dependencies)
.execute(self.inner())
.map_err(DatabaseError::InsertPackageDepFailed)
}

/// Fetch the dependencies for a given package version.
pub fn get_dependencies_for_package_version(
&mut self,
package_version_id: Uuid,
) -> Result<Vec<models::PackageDep>, DatabaseError> {
schema::package_dependencies::table
.filter(
schema::package_dependencies::dependent_package_version_id.eq(package_version_id),
)
.select(models::PackageDep::as_returning())
.load::<models::PackageDep>(self.inner())
.map_err(|err| DatabaseError::NotFound(package_version_id.to_string(), err))
}
}
48 changes: 35 additions & 13 deletions src/db/package_version.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::error::DatabaseError;
use super::{models, schema, DbConn};
use crate::api::pagination::{PaginatedResponse, Pagination};
use crate::api::publish::PublishRequest;
use crate::handlers::publish::PublishInfo;
use crate::models::{ApiToken, CountResult, FullPackage, PackagePreview};
use chrono::{DateTime, Utc};
use diesel::prelude::*;
Expand All @@ -14,10 +14,10 @@ impl DbConn {
pub fn new_package_version(
&mut self,
api_token: &ApiToken,
request: &PublishRequest,
publish_info: &PublishInfo,
) -> Result<models::PackageVersion, DatabaseError> {
// Check if the package exists.
let pkg_name = request.package_name.clone();
let pkg_name = publish_info.package_name.clone();
let package = if let Some(existing_package) = schema::packages::table
.filter(schema::packages::package_name.eq(pkg_name.clone()))
.select(schema::packages::all_columns)
Expand Down Expand Up @@ -45,7 +45,7 @@ impl DbConn {
Ok(saved_package)
}?;

let urls = request
let urls = publish_info
.urls
.iter()
.map(|url| Some(url.to_string()))
Expand All @@ -56,15 +56,17 @@ impl DbConn {
package_id: package.id,
publish_token: api_token.id,
published_by: api_token.user_id,
upload_id: request.upload_id,
num: request.num.clone(),
package_description: request.package_description.clone(),
repository: request.repository.clone().map(|url| url.to_string()),
documentation: request.documentation.clone().map(|url| url.to_string()),
homepage: request.homepage.clone().map(|url| url.to_string()),
upload_id: publish_info.upload_id,
num: publish_info.num.clone(),
package_description: publish_info.package_description.clone(),
repository: publish_info.repository.clone().map(|url| url.to_string()),
documentation: publish_info
.documentation
.clone()
.map(|url| url.to_string()),
homepage: publish_info.homepage.clone().map(|url| url.to_string()),
license: publish_info.license.clone(),
urls,
readme: request.readme.clone(),
license: request.license.clone(),
};

let saved_version = diesel::insert_into(schema::package_versions::table)
Expand All @@ -74,7 +76,7 @@ impl DbConn {
.map_err(|err| {
DatabaseError::InsertPackageVersionFailed(
pkg_name.clone(),
request.num.clone(),
publish_info.num.clone(),
err,
)
})?;
Expand Down Expand Up @@ -109,6 +111,26 @@ impl DbConn {
.map_err(|err| DatabaseError::NotFound(name.clone(), err))
}

/// Fetch a package given the package ID.
pub fn get_package_version(
&mut self,
pkg_name: String,
version: String,
) -> Result<models::PackageVersion, DatabaseError> {
schema::package_versions::table
.inner_join(
schema::packages::table
.on(schema::packages::id.eq(schema::package_versions::package_id)),
)
.filter(schema::package_versions::num.eq(version.clone()))
.filter(schema::packages::package_name.eq(pkg_name.clone()))
.select(models::PackageVersion::as_returning())
.first::<models::PackageVersion>(self.inner())
.map_err(|err| {
DatabaseError::NotFound(format!("Package {pkg_name} version {version}"), err)
})
}

/// Fetch the most recently updated packages.
pub fn get_recently_updated(&mut self) -> Result<Vec<PackagePreview>, DatabaseError> {
let packages = diesel::sql_query(
Expand Down
2 changes: 2 additions & 0 deletions src/handlers/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod publish;
pub mod upload;
Loading

0 comments on commit 34c0fe9

Please sign in to comment.