diff --git a/.env b/.env index 15b7d07..ba74042 100644 --- a/.env +++ b/.env @@ -2,5 +2,4 @@ POSTGRES_USER="postgres" POSTGRES_PASSWORD="localpw" POSTGRES_URI="localhost" POSTGRES_PORT="5432" -POSTGRES_DB_NAME="forc_pub" -DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_URI}/${POSTGRES_DB_NAME}" +POSTGRES_DB_NAME="forc_pub" \ No newline at end of file diff --git a/README.md b/README.md index 9a6320d..1773e08 100644 --- a/README.md +++ b/README.md @@ -3,4 +3,111 @@ [![docs](https://docs.rs/forc/badge.svg)](https://docs.rs/forc/) [![discord](https://img.shields.io/badge/chat%20on-discord-orange?&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/xfpK4Pe) -Welcome to the GitHub repository for [forc.pub](https://forc.pub/), the official package registry for the Sway programming language. \ No newline at end of file +Welcome to the GitHub repository for [forc.pub](https://forc.pub/), the official package registry for the Sway programming language. + +## How it Works + +`forc.pub` has a simple multi-threaded Rocket backend server which stores and fetches package metadata in a Postgres database. Packages are stored in a dedicated IPFS network, and package metadata is indexed and stored in [forc.pub-index](https://github.com/FuelLabs/forc.pub-index). + +The frontend uses React and TypeScript. + +## Forc Documentation + +For user documentation, including installing release builds, see: . + +## Building from Source + +This section is for local development of `forc.pub`. + +### Dependencies and Tools + +`forc.pub` is built in Rust and TypeScript. To begin, install the Rust toolchain following instructions at . Then configure your Rust toolchain to use Rust `stable`: + +```sh +rustup default stable +``` + +If not already done, add the Cargo bin directory to your `PATH` by adding the following line to `~/.profile` and restarting the shell session. + +```sh +export PATH="${HOME}/.cargo/bin:${PATH}" +``` + +You will also need to install [Node.js](https://nodejs.org/en/learn/getting-started/how-to-install-nodejs). + +To run the Postgres database locally, you will need [Docker](https://docs.docker.com/engine/install/). + +To connect to the database, you will need the [Diesel CLI](https://diesel.rs/guides/getting-started). + +Diesel is the Rust ORM used to create and run database migrations. It requires a separate C library called `libpq` to be installed as well. + +```sh +# Mac only +brew install libpq + +# Ubuntu only +apt-get install libpq5 + +# Install diesel CLI +cargo install diesel_cli --no-default-features --features postgres + +# On macOS-arm64, you may need additional rust flags: +RUSTFLAGS='-L /opt/homebrew/opt/libpq/lib' cargo install diesel_cli --no-default-features --features postgres +``` + +It is also recommended to install a Postgres client like [DBeaver](https://dbeaver.io/) to connect and inspect the database while developing locally. + +### Building the `forc.pub` server + +Clone the repository and build the Sway toolchain: + +```sh +git clone git@github.com:FuelLabs/forc.pub.git +cd forc.pub +cargo build +``` + +Confirm the server built successfully: + +```sh +cargo run --bin forc.pub +``` + +### Running the `forc.pub` server + +Before starting the server, the local database must be up and running. + +```sh +./scripts/start_local_db.sh +``` + +Now we can run the server with: + +```sh +cargo run +``` + +Alternatively, the server can be run locally with Docker, as it is in the deployed environment. + +```sh +./scripts/start_local_server.sh + +# Force the server image to be rebuilt +./scripts/start_local_server.sh -f +``` + +### Running the Frontend + +The frontend requires npm and node to be installed. + +```sh +cd app +npm i +npm start +``` + +This will open http://localhost:3000 in your browser. By default, it will use the local server endpoint, so the local server must be running. + +## Contributing + +We are not currently accepting contributions to `forc.pub` as the MVP is still being developed. diff --git a/app/src/constants.ts b/app/src/constants.ts index 9fc5846..df8601f 100644 --- a/app/src/constants.ts +++ b/app/src/constants.ts @@ -1 +1,2 @@ -export const SERVER_URI = process.env.REACT_APP_SERVER_URI ?? "http://localhost:8080" \ No newline at end of file +export const SERVER_URI = process.env.REACT_APP_SERVER_URI ?? "http://localhost:8080"; +export const REDIRECT_URI = process.env.REACT_APP_REDIRECT_URI ?? "http://localhost:3000"; \ No newline at end of file diff --git a/app/src/features/toolbar/components/UserButton.tsx b/app/src/features/toolbar/components/UserButton.tsx index 0bb7a4d..42b650f 100644 --- a/app/src/features/toolbar/components/UserButton.tsx +++ b/app/src/features/toolbar/components/UserButton.tsx @@ -8,6 +8,7 @@ import { useNavigate } from 'react-router-dom'; import { useGithubAuth } from '../hooks/useGithubAuth'; import { useLocalSession } from '../../../utils/localStorage'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; +import { REDIRECT_URI } from '../../../constants'; export const GITHUB_CLIENT_ID = 'Iv1.ebdf596c6c548759'; @@ -98,7 +99,7 @@ function UserButton() { ].join(','); window.open( - `https://github.com/login/oauth/authorize?client_id=${GITHUB_CLIENT_ID}`, + `https://github.com/login/oauth/authorize?client_id=${GITHUB_CLIENT_ID}&redirect_uri=${REDIRECT_URI}`, undefined, windowDimensions ); diff --git a/app/src/features/toolbar/hooks/useGithubAuth.ts b/app/src/features/toolbar/hooks/useGithubAuth.ts index 50a7550..122532a 100644 --- a/app/src/features/toolbar/hooks/useGithubAuth.ts +++ b/app/src/features/toolbar/hooks/useGithubAuth.ts @@ -112,12 +112,13 @@ export function useGithubAuth(): [AuthenticatedUser | null, () => void] { response.user && setGithubUser(response.user); if (response.error) { console.log('session error: ', response.error); + clearSessionId(); } }) .catch((error) => { console.log('Unexpected error: ', error); }); - }, [githubUser, sessionId, setGithubUser]); + }, [githubUser, sessionId, setGithubUser, clearSessionId]); return [githubUser, logout]; } diff --git a/deployment/Dockerfile b/deployment/Dockerfile index 4891a79..03c136d 100644 --- a/deployment/Dockerfile +++ b/deployment/Dockerfile @@ -32,7 +32,7 @@ RUN cargo build FROM ubuntu:22.04 as run RUN apt-get update -y \ - && apt-get install -y --no-install-recommends ca-certificates curl git \ + && apt-get install -y --no-install-recommends ca-certificates curl git libpq5 \ # Clean up && apt-get autoremove -y \ && apt-get clean -y \ diff --git a/scripts/start_local_db.sh b/scripts/start_local_db.sh index 4188f4f..a9af97e 100755 --- a/scripts/start_local_db.sh +++ b/scripts/start_local_db.sh @@ -1,5 +1,10 @@ #!/bin/bash +# Source environment variables +source .env +NETWORK_NAME="forc_pub_net" +CONTAINER_NAME="forc_pub_db" + # Check if Docker is installed if ! command -v docker &> /dev/null; then echo "Docker is not installed. Please install Docker to run this script." @@ -7,15 +12,25 @@ if ! command -v docker &> /dev/null; then fi # Check if the PostgreSQL container is already running -if docker ps --format '{{.Names}}' | grep -q '^postgres$'; then +if docker ps --format '{{.Names}}' | grep -q ^$CONTAINER_NAME$; then echo "PostgreSQL container is already running." exit 0 fi -# Source environment variables -source .env +# Create docker network if it does not exist +if [ -z $(docker network ls --filter name=^${NETWORK_NAME}$ --format="{{ .Name }}") ] ; then + echo "Creating docker network ${NETWORK_NAME}." + docker network create $NETWORK_NAME +fi # Start PostgreSQL container -docker run --name $POSTGRES_USER -e POSTGRES_PASSWORD=$POSTGRES_PASSWORD -e POSTGRES_DB=$POSTGRES_DB_NAME -d -p $POSTGRES_PORT:$POSTGRES_PORT postgres +docker run \ + --rm -d \ + --name $CONTAINER_NAME \ + --network $NETWORK_NAME \ + -e POSTGRES_PASSWORD=$POSTGRES_PASSWORD \ + -e POSTGRES_DB=$POSTGRES_DB_NAME \ + -p $POSTGRES_PORT:$POSTGRES_PORT \ + postgres echo "PostgreSQL container started successfully." \ No newline at end of file diff --git a/scripts/start_local_server.sh b/scripts/start_local_server.sh new file mode 100755 index 0000000..4b2fc86 --- /dev/null +++ b/scripts/start_local_server.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +# Start the PostgreSQL container +./scripts/start_local_db.sh + +# Source environment variables +source .env + +CONTAINER_NAME="forc_pub_dev" +NETWORK_NAME="forc_pub_net" +DB_CONTAINER_NAME="forc_pub_db" +FORCE_REBUILD=false + +# Check for command line arguments +while getopts "f" opt; do + case ${opt} in + f) + FORCE_REBUILD=true + ;; + \?) + echo "Usage: $0 [-f]" + exit 1 + ;; + esac +done + +# Check if Docker image exists +if $FORCE_REBUILD || [[ "$(docker images -q $DOCKER_IMAGE 2> /dev/null)" == "" ]]; then + echo "Building Docker image $DOCKER_IMAGE..." + + # Build Docker image + docker build -t $CONTAINER_NAME -f deployment/Dockerfile . + + # Check if build was successful + if [ $? -eq 0 ]; then + echo "Docker image $DOCKER_IMAGE built successfully." + else + echo "Failed to build Docker image $DOCKER_IMAGE." + exit 1 + fi +else + echo "Docker image $DOCKER_IMAGE already exists. Use -f flag to force rebuild." +fi + +# Remove the container if it exists +if [[ "$(docker ps -aqf name=$CONTAINER_NAME)" ]]; then + # Stop the container if it's running + if [[ "$(docker ps -q -f name=$CONTAINER_NAME)" ]]; then + echo "Stopping container $CONTAINER_NAME..." + docker stop $CONTAINER_NAME + if [ $? -eq 0 ]; then + echo "Container $CONTAINER_NAME stopped successfully." + else + echo "Failed to stop container $CONTAINER_NAME." + exit 1 + fi + fi +fi + +# Start the Docker container on the same network as the PostgreSQL container +docker run \ + --rm -d \ + --name $CONTAINER_NAME \ + --network $NETWORK_NAME \ + -p 8080:8080 \ + -e POSTGRES_USER=$POSTGRES_USER \ + -e POSTGRES_PASSWORD=$POSTGRES_PASSWORD \ + -e POSTGRES_URI=$DB_CONTAINER_NAME \ + -e POSTGRES_DB_NAME=$POSTGRES_DB_NAME \ + $CONTAINER_NAME + +echo "Server container started successfully." \ No newline at end of file diff --git a/src/db/mod.rs b/src/db/mod.rs index 327cf5d..3014798 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -27,10 +27,8 @@ impl Default for Database { impl Database { pub fn new() -> Self { // Create a connection pool - dotenv().ok(); - let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); let pool = Pool::builder() - .build(ConnectionManager::::new(database_url)) + .build(ConnectionManager::::new(db_url())) .expect("db connection pool"); // Run migrations @@ -53,3 +51,12 @@ impl Database { pub(crate) fn string_to_uuid(s: String) -> Result { Uuid::parse_str(s.as_str()).map_err(|_| DatabaseError::InvalidUuid(s)) } + +fn db_url() -> String { + dotenv().ok(); + let user = env::var("POSTGRES_USER").expect("POSTGRES_USER must be set"); + let password = env::var("POSTGRES_PASSWORD").expect("POSTGRES_PASSWORD must be set"); + let uri = env::var("POSTGRES_URI").expect("POSTGRES_URI must be set"); + let db_name = env::var("POSTGRES_DB_NAME").expect("POSTGRES_DB_NAME must be set"); + format!("postgres://{user}:{password}@{uri}/{db_name}") +}