Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: completing the rewrite #145

Merged
merged 6 commits into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ mock_server/target
*.rock
*.nix

/crates/ratings_new/Cargo.lock
/crates/ratings/Cargo.lock

/proto/*.rs
venv/
build/
Expand Down
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 0 additions & 7 deletions crates/ratings_new/Cargo.lock

This file was deleted.

8 changes: 7 additions & 1 deletion crates/ratings_new/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ db_tests = []
[dependencies]
dotenvy = "0.15"
envy = "0.4"
http = "1.1.0"
jsonwebtoken = "9.2"
prost = "0.13.3"
prost-types = "0.13.3"
Expand All @@ -28,10 +29,15 @@ time = "0.3"
tokio = { version = "1.40.0", features = ["full"] }
tonic = "0.12.2"
tonic-reflection = "0.12.2"
tower = "0.5.1"
tracing = "0.1.40"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt", "json"] }

[dev-dependencies]
anyhow = "1.0.93"
futures = "0.3"
rand = "0.8"
sha2 = "0.10"
simple_test_case = "1.2.0"

[build-dependencies]
Expand Down
10 changes: 5 additions & 5 deletions crates/ratings_new/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ fn init_proto() -> Result<(), Box<dyn std::error::Error>> {
);

let files = &[
"../../proto/ratings_features_app.proto",
"../../proto/ratings_features_chart.proto",
"../../proto/ratings_features_user.proto",
"../../proto/ratings_features_common.proto",
"proto/ratings_features_app.proto",
"proto/ratings_features_chart.proto",
"proto/ratings_features_user.proto",
"proto/ratings_features_common.proto",
];

tonic_build::configure()
Expand All @@ -27,7 +27,7 @@ fn init_proto() -> Result<(), Box<dyn std::error::Error>> {
"Category",
r#"#[strum(serialize_all = "kebab_case", ascii_case_insensitive)]"#,
)
.compile(files, &["../../proto"])?;
.compile(files, &["proto"])?;

Ok(())
}
Expand Down
17 changes: 17 additions & 0 deletions crates/ratings_new/proto/ratings_features_app.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
syntax = "proto3";

package ratings.features.app;

import "ratings_features_common.proto";

service App {
rpc GetRating (GetRatingRequest) returns (GetRatingResponse) {}
}

message GetRatingRequest {
string snap_id = 1;
}

message GetRatingResponse {
ratings.features.common.Rating rating = 1;
}
57 changes: 57 additions & 0 deletions crates/ratings_new/proto/ratings_features_chart.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
syntax = "proto3";

package ratings.features.chart;

import "ratings_features_common.proto";

service Chart {
rpc GetChart (GetChartRequest) returns (GetChartResponse) {}
}

message GetChartRequest {
Timeframe timeframe = 1;
optional Category category = 2;
}

message GetChartResponse {
Timeframe timeframe = 1;
repeated ChartData ordered_chart_data = 2;
optional Category category = 3;
}

message ChartData {
float raw_rating = 1;
ratings.features.common.Rating rating = 2;
}

enum Timeframe {
TIMEFRAME_UNSPECIFIED = 0;
TIMEFRAME_WEEK = 1;
TIMEFRAME_MONTH = 2;
}

// The categories that can be selected, these
// are taken directly from `curl -sS -X GET --unix-socket /run/snapd.socket "http://localhost/v2/categories"`
// On 2024-02-03, it may need to be kept in sync.
enum Category {
ART_AND_DESIGN = 0;
BOOK_AND_REFERENCE = 1;
DEVELOPMENT = 2;
DEVICES_AND_IOT = 3;
EDUCATION = 4;
ENTERTAINMENT = 5;
FEATURED = 6;
FINANCE = 7;
GAMES = 8;
HEALTH_AND_FITNESS = 9;
MUSIC_AND_AUDIO = 10;
NEWS_AND_WEATHER = 11;
PERSONALISATION = 12;
PHOTO_AND_VIDEO = 13;
PRODUCTIVITY = 14;
SCIENCE = 15;
SECURITY = 16;
SERVER_AND_CLOUD = 17;
SOCIAL = 18;
UTILITIES = 19;
}
18 changes: 18 additions & 0 deletions crates/ratings_new/proto/ratings_features_common.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
syntax = "proto3";

package ratings.features.common;

message Rating {
string snap_id = 1;
uint64 total_votes = 2;
RatingsBand ratings_band = 3;
}

enum RatingsBand {
VERY_GOOD = 0;
GOOD = 1;
NEUTRAL = 2;
POOR = 3;
VERY_POOR = 4;
INSUFFICIENT_VOTES = 5;
}
53 changes: 53 additions & 0 deletions crates/ratings_new/proto/ratings_features_user.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
syntax = "proto3";

package ratings.features.user;

import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";

service User {
rpc Authenticate (AuthenticateRequest) returns (AuthenticateResponse) {}

rpc Delete (google.protobuf.Empty) returns (google.protobuf.Empty) {}
rpc Vote (VoteRequest) returns (google.protobuf.Empty) {}
rpc ListMyVotes (ListMyVotesRequest) returns (ListMyVotesResponse) {}
rpc GetSnapVotes(GetSnapVotesRequest) returns (GetSnapVotesResponse) {}
}

message AuthenticateRequest {
// sha256([$user:$machineId])
string id = 1;
}

message AuthenticateResponse {
string token = 1;
}

message ListMyVotesRequest {
string snap_id_filter = 1;
}

message ListMyVotesResponse {
repeated Vote votes = 1;
}

message GetSnapVotesRequest {
string snap_id = 1;
}

message GetSnapVotesResponse {
repeated Vote votes = 1;
}

message Vote {
string snap_id = 1;
int32 snap_revision = 2;
bool vote_up = 3;
google.protobuf.Timestamp timestamp = 4;
}

message VoteRequest {
string snap_id = 1;
int32 snap_revision = 2;
bool vote_up = 3;
}
10 changes: 2 additions & 8 deletions crates/ratings_new/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,14 @@ use serde::Deserialize;
/// Configuration for the general app center ratings backend service.
#[derive(Deserialize, Debug, Clone)]
pub struct Config {
/// Environment variables to use
pub env: String,
/// The host configuration
pub host: String,
/// The JWT secret value
pub jwt_secret: SecretString,
/// Log level to use
pub log_level: String,
/// The service name
pub name: String,
/// The port to run on
pub port: u16,
/// The URI of the postgres database
pub postgres_uri: String,
/// The JWT secret value
pub jwt_secret: SecretString,
/// The base URI for snapcraft.io
pub snapcraft_io_uri: String,
}
Expand Down
77 changes: 9 additions & 68 deletions crates/ratings_new/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,10 @@
//! Application level context & state
use crate::config::Config;
use jsonwebtoken::{EncodingKey, Header};
use secrecy::{ExposeSecret, SecretString};
use serde::{Deserialize, Serialize};
use crate::{
config::Config,
jwt::{Error, JwtEncoder},
};
use std::{collections::HashMap, sync::Arc};
use time::{Duration, OffsetDateTime};
use tokio::sync::{Mutex, Notify};
use tracing::error;

/// How many days until JWT info expires
static JWT_EXPIRY_DAYS: i64 = 1;

/// Errors that can happen while encoding and signing tokens with JWT.
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("jwt: error decoding secret: {0}")]
DecodeSecretError(#[from] jsonwebtoken::errors::Error),

#[error(transparent)]
Envy(#[from] envy::Error),

#[error("jwt: an error occurred, but the reason was erased for security reasons")]
Erased,
}

pub struct Context {
pub config: Config,
Expand All @@ -33,55 +15,14 @@ pub struct Context {
}

impl Context {
pub fn new(config: &Config) -> Result<Self, Error> {
pub fn new(config: Config) -> Result<Self, Error> {
let jwt_encoder = JwtEncoder::from_secret(&config.jwt_secret)?;

Ok(Self {
config: Config::load()?,
jwt_encoder: JwtEncoder::from_secret(&config.jwt_secret)?,
config,
jwt_encoder,
http_client: reqwest::Client::new(),
category_updates: Default::default(),
})
}
}

/// Information representating a claim on a specific subject at a specific time
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Claims {
/// The subject
pub sub: String,
/// The expiration time
pub exp: usize,
}

impl Claims {
/// Creates a new claim with the current datetime for the subject given by `sub`.
pub fn new(sub: String) -> Self {
let exp = OffsetDateTime::now_utc() + Duration::days(JWT_EXPIRY_DAYS);
let exp = exp.unix_timestamp() as usize;

Self { sub, exp }
}
}

pub struct JwtEncoder {
encoding_key: EncodingKey,
}

impl JwtEncoder {
pub fn from_secret(secret: &SecretString) -> Result<JwtEncoder, Error> {
let encoding_key = EncodingKey::from_base64_secret(secret.expose_secret())?;

Ok(Self { encoding_key })
}

pub fn encode(&self, sub: String) -> Result<String, Error> {
let claims = Claims::new(sub);

match jsonwebtoken::encode(&Header::default(), &claims, &self.encoding_key) {
Ok(s) => Ok(s),
Err(e) => {
error!("unable to encode jwt: {e}");
Err(Error::Erased)
}
}
}
}
Loading
Loading