Skip to content

Commit

Permalink
Home Page + Profile Page (#23)
Browse files Browse the repository at this point in the history
* move existing schemas into frontend/backend folders

* create home page and profile page schemas

* scaffold the API routes

* implement ProfilePageService

* implement HomePageServiceImpl

* show header buttons only when logged in

* Add tweet component

* almost done replacing profile page with frontend model

* make the profile page look good

* sort by time

* homepage with new profiles column

* colors

* add a tweet box

* nit

* delete unused code
  • Loading branch information
wcygan authored Oct 18, 2023
1 parent c561dc9 commit 349d1db
Show file tree
Hide file tree
Showing 50 changed files with 723 additions and 268 deletions.
8 changes: 8 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ resolver = "2"

[workspace.dependencies]
futures = "0.3.28"
glob = "0.3.1"
serde = { version = "1.0.188", features = ["derive"] }
anyhow = "1.0.69"
thiserror = "1.0.48"
Expand Down
1 change: 1 addition & 0 deletions common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ license = "MIT"
path = "src/lib.rs"

[dependencies]
schemas = { path = "../schemas" }
serde.workspace=true
anyhow.workspace=true
futures.workspace=true
Expand Down
14 changes: 14 additions & 0 deletions common/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use schemas::profile::Profile;
use schemas::tweet::Tweet;

pub fn tweet_details(tweet: Tweet, profile: &Profile) -> schemas::frontend::Tweet {
schemas::frontend::Tweet {
tweet_id: tweet.tweet_id,
user_id: tweet.user_id,
first_name: profile.first_name.clone(),
last_name: profile.last_name.clone(),
message: tweet.message,
created_at: tweet.created_at,
parent_tweet_id: tweet.parent_tweet_id,
}
}
1 change: 1 addition & 0 deletions common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use tokio::task::JoinHandle;

pub mod db;
pub mod helpers;
mod service;

pub use service::Service;
Expand Down
2 changes: 1 addition & 1 deletion documentation/TEST_GRPC_FROM_TERMINAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ You will need [grpcurl](https://github.com/fullstorydev/grpcurl) installed.

Next, find example requests in the [grpcurl-examples](../grpcurl-examples) folder.

For example, [profile.md](../grpcurl-examples/profile.md) contains examples for [profile.proto](../schemas/protos/profile.proto).
For example, [profile.md](../grpcurl-examples/profile.md) contains examples for [profile.proto](../schemas/protos/backend/profile.proto).
2 changes: 1 addition & 1 deletion grpcurl-examples/profile.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[grpcurl](https://github.com/fullstorydev/grpcurl) examples for [profile.proto](../schemas/protos/profile.proto)
[grpcurl](https://github.com/fullstorydev/grpcurl) examples for [profile.proto](../schemas/protos/backend/profile.proto)

## Create

Expand Down
12 changes: 10 additions & 2 deletions profiles-backend/src/service/profile.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use common::db::mongo::{collect_deserialize, MongoCollection, MongoDB};
use mongodb::bson;
use mongodb::bson::doc;
use mongodb::options::FindOptions;
use std::time::Instant;
use tonic::{Request, Response, Status};
use tracing::info;
Expand Down Expand Up @@ -77,7 +78,9 @@ impl ProfileService for ProfileServiceImpl {
"$in": user_ids,
},
},
None,
FindOptions::builder()
.sort(doc! { "joined_at": -1 })
.build(),
)
.await
.map_err(|_| Status::internal("Failed to get profiles"))?;
Expand All @@ -100,7 +103,12 @@ impl ProfileService for ProfileServiceImpl {
.client
.database(MongoDB::Profiles.name())
.collection(MongoCollection::Profiles.name())
.find(None, None)
.find(
None,
FindOptions::builder()
.sort(doc! { "joined_at": -1 })
.build(),
)
.await
.map_err(|_| Status::internal("Failed to get profiles"))?;

Expand Down
1 change: 1 addition & 0 deletions schemas/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ tonic.workspace = true

[build-dependencies]
tonic-build = "0.10.0"
glob.workspace = true
13 changes: 13 additions & 0 deletions schemas/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Schemas

This directory contains the data models for the frontend and backend.

The schemas are written using [protobuf](https://protobuf.dev/) definitions.

## Backend

The backend schemas reside in the [backend](./protos/backend) folder. The Rust code for each schema is generated by running `cargo build`; it invokes a build script in [build.rs](./build.rs) to compile the proto files into rust.

## Frontend

The frontend schemas reside in the [frontend](./protos/frontend) folder. The JavaScript code for each schema is generated by running `protoc`, which we can run using [proto.sh](../scripts/proto.sh).
18 changes: 12 additions & 6 deletions schemas/build.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
let proto_files = vec![
"protos/account.proto",
"protos/profile.proto",
"protos/tweet.proto",
];
extern crate glob;

use glob::glob;
use std::error::Error;
use tonic_build;

fn main() -> Result<(), Box<dyn Error>> {
let proto_files: Vec<String> = glob("protos/**/*.proto")?
.filter_map(Result::ok)
.map(|path| path.display().to_string())
.collect();

tonic_build::configure()
.build_server(true)
.build_client(true)
.compile(&proto_files, &["protos/"])?;

Ok(())
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ message Tweet {

// The time the tweet was created.
google.protobuf.Timestamp created_at = 4;

// The ID of the tweet who this tweet is replying to.
string parent_tweet_id = 5;
}

// A response containing a batch of tweets.
Expand Down
22 changes: 22 additions & 0 deletions schemas/protos/frontend/home_page.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
syntax = "proto3";

import "google/protobuf/empty.proto";
import "backend/profile.proto";
import "frontend/tweet.proto";

package frontend;

// The homepage service is responsible for loading the homepage.
service HomePageService {
// Load the homepage.
rpc GetHomePage(google.protobuf.Empty) returns (HomePage);
}

// The homepage message contains the tweets and profiles to display on the homepage.
message HomePage {
// The tweets to display on the homepage.
repeated Tweet tweets = 1;

// The profiles to display on the homepage.
repeated profile.Profile profiles = 2;
}
26 changes: 26 additions & 0 deletions schemas/protos/frontend/profile_page.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
syntax = "proto3";

import "frontend/tweet.proto";
import "backend/profile.proto";

package frontend;

service ProfilePageService {
// Gets a user's profile page.
rpc GetProfilePage(GetProfilePageRequest) returns (ProfilePage);
}

// The user's profile page.
message ProfilePage {
// The user's profile information.
profile.Profile profile = 1;

// The user's Tweets.
repeated Tweet tweets = 2;
}

// Request for getting a user's profile page.
message GetProfilePageRequest {
// The user ID.
string user_id = 1;
}
28 changes: 28 additions & 0 deletions schemas/protos/frontend/tweet.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
syntax = "proto3";

import "google/protobuf/timestamp.proto";

package frontend;

message Tweet {
// The ID of the tweet.
string tweet_id = 1;

// The ID of the user who posted the tweet.
string user_id = 2;

// The ID of the user who posted the tweet.
string first_name = 3;

// The ID of the user who posted the tweet.
string last_name = 4;

// The message of the tweet.
string message = 5;

// The time the tweet was created.
google.protobuf.Timestamp created_at = 6;

// The ID of the tweet who this tweet is replying to.
string parent_tweet_id = 7;
}
4 changes: 4 additions & 0 deletions schemas/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ pub mod profile {
pub mod tweet {
include!(concat!(env!("OUT_DIR"), "/tweet.rs"));
}

pub mod frontend {
include!(concat!(env!("OUT_DIR"), "/frontend.rs"));
}
2 changes: 1 addition & 1 deletion scripts/proto.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Generate the javascript code from the proto files
# Run this script from the root of the project
protoc --proto_path=schemas/protos/ --js_out=import_style=commonjs,binary:twote-web/src/proto --grpc-web_out=import_style=commonjs,mode=grpcwebtext:twote-web/src/proto schemas/protos/*.proto
protoc --proto_path=schemas/protos/ --js_out=import_style=commonjs,binary:twote-web/src/proto --grpc-web_out=import_style=commonjs,mode=grpcwebtext:twote-web/src/proto schemas/protos/**/*.proto
2 changes: 1 addition & 1 deletion scripts/windows/proto.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ $ErrorActionPreference = "Stop"
--proto_path='schemas/protos/' `
--js_out='import_style=commonjs,binary:twote-web/src/proto' `
--grpc-web_out='import_style=commonjs,mode=grpcwebtext:twote-web/src/proto' `
'schemas/protos/*.proto'
'schemas/protos/**/*.proto'
20 changes: 17 additions & 3 deletions tweets-backend/src/service/tweet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::time::Instant;

use mongodb::bson;
use mongodb::bson::doc;
use mongodb::options::FindOptions;
use tonic::{Request, Response, Status};
use tracing::info;
use uuid::Uuid;
Expand Down Expand Up @@ -76,7 +77,9 @@ impl TweetService for TweetServiceImpl {
"$in": tweet_id,
},
},
None,
FindOptions::builder()
.sort(doc! { "created_at": -1 })
.build(),
)
.await
.map_err(|_| Status::internal("Failed to batch get tweets"))?;
Expand All @@ -101,7 +104,12 @@ impl TweetService for TweetServiceImpl {
.client
.database(MongoDB::Tweets.name())
.collection(MongoCollection::Tweets.name())
.find(None, None)
.find(
None,
FindOptions::builder()
.sort(doc! { "created_at": -1 })
.build(),
)
.await
.map_err(|_| Status::internal("Failed to get all tweets"))?;

Expand Down Expand Up @@ -130,7 +138,9 @@ impl TweetService for TweetServiceImpl {
doc! {
"user_id": user_id,
},
None,
FindOptions::builder()
.sort(doc! { "created_at": -1 })
.build(),
)
.await
.map_err(|_| Status::internal("Failed to get all tweets by user"))?;
Expand All @@ -152,6 +162,7 @@ struct TweetsDao {
user_id: String,
message: String,
created_at: bson::Timestamp,
parent_tweet_id: Option<String>,
}

impl From<TweetsDao> for Tweet {
Expand All @@ -166,6 +177,7 @@ impl From<TweetsDao> for Tweet {
user_id: val.user_id,
message: val.message,
created_at: Some(ts),
parent_tweet_id: val.parent_tweet_id.unwrap_or("".to_string()),
}
}
}
Expand All @@ -177,6 +189,7 @@ impl From<TweetsDao> for bson::Document {
"user_id": &val.user_id,
"message": &val.message,
"created_at": &val.created_at,
"parent_tweet_id": &val.parent_tweet_id,
}
}
}
Expand All @@ -193,6 +206,7 @@ impl TweetsDao {
user_id: request.user_id,
message: request.message,
created_at,
parent_tweet_id: None,
}
}
}
23 changes: 11 additions & 12 deletions twote-api/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
use tonic::transport::Server;
use tracing::info;

use crate::service::home_page::HomePageServiceImpl;
use crate::service::login::AccountServiceImpl;
use crate::service::profile_page::ProfilePageServiceImpl;
use common::authentication::AuthMiddleware;
use common::Service::TwoteApi;
use schemas::account::account_service_server::AccountServiceServer;
use schemas::profile::profile_service_server::ProfileServiceServer;
use schemas::tweet::tweet_service_server::TweetServiceServer;

use crate::service::login::AccountServiceImpl;
use crate::service::profile::ProfileServiceImpl;
use crate::service::tweet::TweetServiceImpl;
use schemas::frontend::home_page_service_server::HomePageServiceServer;
use schemas::frontend::profile_page_service_server::ProfilePageServiceServer;

mod service;

Expand All @@ -21,20 +20,20 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.init();

// Create the services
let login_service = AccountServiceServer::new(AccountServiceImpl);
let profile_service = ProfileServiceServer::new(ProfileServiceImpl);
let tweet_service = TweetServiceServer::new(TweetServiceImpl);
let home_page_service = HomePageServiceServer::new(HomePageServiceImpl);
let profile_page_service = ProfilePageServiceServer::new(ProfilePageServiceImpl);
let account_service = AccountServiceServer::new(AccountServiceImpl);
let (_, health_service) = tonic_health::server::health_reporter();

// Start the server
let addr = TwoteApi.socket_addr();
info!("twote-api running on {}", addr);
Server::builder()
.layer(AuthMiddleware::default())
.add_service(home_page_service)
.add_service(profile_page_service)
.add_service(account_service)
.add_service(health_service)
.add_service(login_service)
.add_service(profile_service)
.add_service(tweet_service)
.serve(addr)
.await?;

Expand Down
Loading

0 comments on commit 349d1db

Please sign in to comment.