├── .gitignore ├── backend ├── .gitignore ├── migrations │ ├── 20231230194839_users_table.down.sql │ ├── 20240101210638_qanda.down.sql │ ├── 20231230194839_users_table.up.sql │ └── 20240101210638_qanda.up.sql ├── build.sh ├── src │ ├── lib.rs │ ├── store │ │ ├── mod.rs │ │ ├── general.rs │ │ ├── tag.rs │ │ ├── crypto.rs │ │ ├── answer.rs │ │ ├── users.rs │ │ └── question.rs │ ├── routes │ │ ├── mod.rs │ │ ├── crypto │ │ │ ├── mod.rs │ │ │ ├── coins.rs │ │ │ ├── price.rs │ │ │ └── prices.rs │ │ ├── health.rs │ │ ├── users │ │ │ ├── mod.rs │ │ │ ├── current_user.rs │ │ │ ├── logout.rs │ │ │ ├── activate_account.rs │ │ │ ├── register.rs │ │ │ └── login.rs │ │ └── qa │ │ │ ├── mod.rs │ │ │ ├── answer.rs │ │ │ ├── ask.rs │ │ │ ├── answers.rs │ │ │ └── questions.rs │ ├── models │ │ ├── mod.rs │ │ ├── users.rs │ │ └── qa.rs │ ├── utils │ │ ├── mod.rs │ │ ├── password.rs │ │ ├── middleware.rs │ │ ├── crypto.rs │ │ ├── qa.rs │ │ ├── responses.rs │ │ ├── user.rs │ │ ├── query_constants.rs │ │ ├── email.rs │ │ └── errors.rs │ ├── main.rs │ ├── settings.rs │ └── startup.rs ├── settings │ ├── production.yml │ ├── development.yml │ └── base.yml ├── .env ├── Dockerfile ├── Cargo.toml └── templates │ └── user_welcome.html ├── frontend ├── .npmrc ├── src │ ├── lib │ │ ├── assets │ │ │ ├── css │ │ │ │ └── style.css │ │ │ └── logo.png │ │ ├── stores │ │ │ ├── notification.store.js │ │ │ └── tags.stores.js │ │ ├── utils │ │ │ ├── constants.js │ │ │ ├── select.custom.js │ │ │ └── helpers.js │ │ └── components │ │ │ ├── Transition.svelte │ │ │ ├── footer │ │ │ └── Footer.svelte │ │ │ ├── inputs │ │ │ ├── Text.svelte │ │ │ ├── TextArea.svelte │ │ │ ├── TagCoin.svelte │ │ │ ├── Email.svelte │ │ │ └── Password.svelte │ │ │ ├── ShowError.svelte │ │ │ ├── Loader.svelte │ │ │ ├── Notification.svelte │ │ │ ├── Modal.svelte │ │ │ ├── header │ │ │ └── Header.svelte │ │ │ └── Charts.svelte │ ├── declarations.d.ts │ ├── routes │ │ ├── +layout.server.js │ │ ├── +layout.js │ │ ├── api │ │ │ ├── [question_id] │ │ │ │ └── answers │ │ │ │ │ └── +server.js │ │ │ └── crypto │ │ │ │ └── prices │ │ │ │ └── +server.js │ │ ├── +layout.svelte │ │ ├── questions │ │ │ ├── [id] │ │ │ │ ├── delete │ │ │ │ │ ├── +page.server.js │ │ │ │ │ └── +page.svelte │ │ │ │ ├── update │ │ │ │ │ ├── +page.server.js │ │ │ │ │ └── +page.svelte │ │ │ │ └── +page.server.js │ │ │ └── ask │ │ │ │ ├── +page.server.js │ │ │ │ └── +page.svelte │ │ ├── users │ │ │ ├── activate │ │ │ │ └── [id] │ │ │ │ │ ├── +page.server.js │ │ │ │ │ └── +page.svelte │ │ │ ├── login │ │ │ │ ├── +page.svelte │ │ │ │ └── +page.server.js │ │ │ └── signup │ │ │ │ ├── +page.svelte │ │ │ │ └── +page.server.js │ │ ├── +page.server.js │ │ └── +page.svelte │ ├── index.test.js │ ├── app.d.ts │ ├── hooks.server.js │ ├── app.html │ └── app.css ├── static │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── mstile-150x150.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── browserconfig.xml │ └── site.webmanifest ├── .prettierignore ├── postcss.config.js ├── .gitignore ├── .eslintignore ├── .prettierrc ├── vite.config.js ├── tailwind.config.js ├── .eslintrc.cjs ├── svelte.config.js ├── jsconfig.json ├── README.md └── package.json ├── logo.png ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .env.dev -------------------------------------------------------------------------------- /frontend/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /frontend/src/lib/assets/css/style.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sirneij/cryptoflow/HEAD/logo.png -------------------------------------------------------------------------------- /frontend/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sirneij/cryptoflow/HEAD/frontend/static/favicon.ico -------------------------------------------------------------------------------- /frontend/src/declarations.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.png" { 2 | const value: string; 3 | return value; 4 | } -------------------------------------------------------------------------------- /frontend/src/lib/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sirneij/cryptoflow/HEAD/frontend/src/lib/assets/logo.png -------------------------------------------------------------------------------- /backend/migrations/20231230194839_users_table.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | DROP TABLE IF EXISTS users; -------------------------------------------------------------------------------- /frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore files for PNPM, NPM and YARN 2 | pnpm-lock.yaml 3 | package-lock.json 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /frontend/static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sirneij/cryptoflow/HEAD/frontend/static/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sirneij/cryptoflow/HEAD/frontend/static/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/static/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sirneij/cryptoflow/HEAD/frontend/static/mstile-150x150.png -------------------------------------------------------------------------------- /frontend/static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sirneij/cryptoflow/HEAD/frontend/static/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /frontend/static/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sirneij/cryptoflow/HEAD/frontend/static/android-chrome-192x192.png -------------------------------------------------------------------------------- /frontend/static/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sirneij/cryptoflow/HEAD/frontend/static/android-chrome-512x512.png -------------------------------------------------------------------------------- /backend/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cargo build --release 3 | cargo install sqlx-cli --no-default-features --features native-tls,postgres -------------------------------------------------------------------------------- /backend/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod models; 2 | pub mod routes; 3 | pub mod settings; 4 | pub mod startup; 5 | pub mod store; 6 | pub mod utils; 7 | -------------------------------------------------------------------------------- /backend/src/store/mod.rs: -------------------------------------------------------------------------------- 1 | mod answer; 2 | mod crypto; 3 | mod general; 4 | mod question; 5 | mod tag; 6 | mod users; 7 | 8 | pub use general::Store; 9 | -------------------------------------------------------------------------------- /frontend/src/lib/stores/notification.store.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | export const notification = writable({ message: '', colorName: '' }); 4 | -------------------------------------------------------------------------------- /frontend/src/routes/+layout.server.js: -------------------------------------------------------------------------------- 1 | /** @type {import('./$types').LayoutServerLoad} */ 2 | export async function load({ locals }) { 3 | return { 4 | user: locals.user 5 | }; 6 | } -------------------------------------------------------------------------------- /frontend/src/lib/stores/tags.stores.js: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | /** @type {Array} */ 4 | let tags = []; 5 | 6 | export const selectedTags = writable(tags); 7 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | -------------------------------------------------------------------------------- /frontend/src/index.test.js: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | describe('sum test', () => { 4 | it('adds 1 + 2 to equal 3', () => { 5 | expect(1 + 2).toBe(3); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /frontend/src/routes/+layout.js: -------------------------------------------------------------------------------- 1 | /** @type {import('./$types').LayoutLoad} */ 2 | export async function load({ fetch, url, data }) { 3 | const { user } = data; 4 | return { fetch, url: url.pathname, user }; 5 | } -------------------------------------------------------------------------------- /backend/src/routes/mod.rs: -------------------------------------------------------------------------------- 1 | mod crypto; 2 | mod health; 3 | mod qa; 4 | mod users; 5 | 6 | pub use crypto::crypto_routes; 7 | pub use health::health_check; 8 | pub use qa::qa_routes; 9 | pub use users::users_routes; 10 | -------------------------------------------------------------------------------- /frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 8 | } 9 | -------------------------------------------------------------------------------- /backend/migrations/20240101210638_qanda.down.sql: -------------------------------------------------------------------------------- 1 | -- Add down migration script here 2 | DROP TABLE IF EXISTS question_tags; 3 | DROP TABLE IF EXISTS tags; 4 | DROP TABLE IF EXISTS answers; 5 | DROP TABLE IF EXISTS questions; 6 | DROP FUNCTION IF EXISTS trigger_set_timestamp(); -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()], 6 | test: { 7 | include: ['src/**/*.{test,spec}.{js,ts}'] 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /frontend/static/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | darkMode: 'class', 4 | content: ['./src/**/*.{html,js,svelte,ts}'], 5 | theme: { 6 | extend: { 7 | fontFamily: { 8 | sans: ['Fira Sans', 'sans-serif'], 9 | mono: ['Fira Mono', 'monospace'] 10 | } 11 | } 12 | }, 13 | plugins: [] 14 | }; 15 | -------------------------------------------------------------------------------- /frontend/src/lib/utils/constants.js: -------------------------------------------------------------------------------- 1 | export const BASE_API_URI = import.meta.env.DEV 2 | ? import.meta.env.VITE_BASE_API_URI_DEV 3 | : import.meta.env.VITE_BASE_API_URI_PROD; 4 | 5 | export const NUM_OF_COINS_TO_SHOW = 10; 6 | 7 | export const PASSWORD_ERROR_MESSAGE = 8 | 'Password must be at least 8 characters long and contain at least one letter and one number'; 9 | -------------------------------------------------------------------------------- /frontend/src/lib/components/Transition.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | {#key key} 11 |
12 | 13 |
14 | {/key} 15 | -------------------------------------------------------------------------------- /backend/settings/production.yml: -------------------------------------------------------------------------------- 1 | application: 2 | protocol: https 3 | host: 0.0.0.0 4 | base_url: "" 5 | 6 | debug: false 7 | 8 | secret: 9 | token_expiration: 15 10 | cookie_expiration: 1440 11 | 12 | frontend_url: "" 13 | 14 | superuser: 15 | email: "sirneij@gmail.com" 16 | password: "123456data" 17 | first_name: "John O" 18 | last_name: "Idogun" 19 | -------------------------------------------------------------------------------- /backend/src/models/mod.rs: -------------------------------------------------------------------------------- 1 | mod qa; 2 | mod users; 3 | 4 | pub use qa::{ 5 | Answer, AnswerAuthor, AnswerAuthorQueryResult, CreateAnswer, CreateQuestion, NewAnswer, 6 | NewQuestion, Question, QuestionAuthorWithTags, QuestionAuthorWithTagsQueryResult, Tag, 7 | UpdateAnswer, UpdateQuestion, 8 | }; 9 | pub use users::{ActivateUser, LoggedInUser, LoginUser, NewUser, User, UserVisible}; 10 | -------------------------------------------------------------------------------- /backend/.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL=postgres://postgres:sirneij@localhost:5432/rust_axum_db_dev 2 | APP_ENVIRONMENT=development 3 | APP_DEBUG=true 4 | REDIS_URL=redis://127.0.0.1:6379 5 | COOKIE_SECRET=3bbefd8d24c89aefd3ad0b8b95afd2ea996e47b89d93d4090b481a091b4e73e5543305f2e831d0b47737d9807a1b5b5773dba3bbb63623bd42de84389fbfa3d1 6 | APP_COINGECKO__API_KEY=your_api_key_here 7 | APP_EMAIL__HOST_USER_PASSWORD=your_password_here -------------------------------------------------------------------------------- /backend/src/routes/crypto/mod.rs: -------------------------------------------------------------------------------- 1 | use axum::{routing::get, Router}; 2 | 3 | mod coins; 4 | mod price; 5 | mod prices; 6 | 7 | pub fn crypto_routes() -> Router { 8 | Router::new() 9 | .route("/prices", get(price::crypto_price_handler)) 10 | .route("/coins", get(coins::all_coins)) 11 | .route("/coin_prices", get(prices::get_coin_market_data)) 12 | } 13 | -------------------------------------------------------------------------------- /frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** @type { import("eslint").Linter.Config } */ 2 | module.exports = { 3 | root: true, 4 | extends: ['eslint:recommended', 'plugin:svelte/recommended', 'prettier'], 5 | parserOptions: { 6 | sourceType: 'module', 7 | ecmaVersion: 2020, 8 | extraFileExtensions: ['.svelte'] 9 | }, 10 | env: { 11 | browser: true, 12 | es2017: true, 13 | node: true 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/settings/development.yml: -------------------------------------------------------------------------------- 1 | application: 2 | protocol: http 3 | host: 127.0.0.1 4 | base_url: "http://127.0.0.1" 5 | 6 | debug: true 7 | 8 | frontend_url: "http://localhost:3000" 9 | 10 | secret: 11 | token_expiration: 15 12 | cookie_expiration: 1440 13 | 14 | superuser: 15 | email: "sirneij@gmail.com" 16 | password: "123456data" 17 | first_name: "John Owolabi" 18 | last_name: "Idogun" 19 | -------------------------------------------------------------------------------- /backend/settings/base.yml: -------------------------------------------------------------------------------- 1 | application: 2 | port: 8080 3 | 4 | redis: 5 | pool_max_open: 16 6 | pool_max_idle: 8 7 | pool_timeout_seconds: 1 8 | pool_expire_seconds: 60 9 | 10 | email: 11 | host: "smtp.gmail.com" 12 | host_user: "sirneij@gmail.com" 13 | host_user_password: "" 14 | 15 | interval_of_coin_update: 24 16 | 17 | coingecko: 18 | api_url: "https://api.coingecko.com/api/v3" 19 | api_key: "" 20 | -------------------------------------------------------------------------------- /backend/src/routes/health.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::SuccessResponse; 2 | use axum::{http::StatusCode, response::IntoResponse}; 3 | 4 | #[tracing::instrument] 5 | pub async fn health_check() -> impl IntoResponse { 6 | SuccessResponse { 7 | message: "Rust(Axum) and SvelteKit application is healthy!".to_string(), 8 | status_code: StatusCode::OK.as_u16(), 9 | ..Default::default() 10 | } 11 | .into_response() 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/lib/components/footer/Footer.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |
6 |
7 |

8 | © {year} CryptoFlow. All rights reserved. Built with ❤️ by 9 | John Idogun. 10 |

11 |
12 |
13 | -------------------------------------------------------------------------------- /backend/src/routes/crypto/coins.rs: -------------------------------------------------------------------------------- 1 | use crate::{startup::AppState, utils::CustomAppError}; 2 | use axum::{extract::State, response::IntoResponse}; 3 | 4 | #[axum::debug_handler] 5 | #[tracing::instrument(name = "all_coins", skip(state))] 6 | pub async fn all_coins(State(state): State) -> Result { 7 | let coins = state.db_store.get_all_coins_from_db().await?; 8 | 9 | Ok(axum::Json(coins).into_response()) 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/lib/components/inputs/Text.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 15 | -------------------------------------------------------------------------------- /frontend/src/routes/api/[question_id]/answers/+server.js: -------------------------------------------------------------------------------- 1 | import { BASE_API_URI } from '$lib/utils/constants'; 2 | import { json } from '@sveltejs/kit'; 3 | 4 | /** @type {import('./$types').RequestHandler} */ 5 | export async function GET({ fetch, params }) { 6 | const apiURL = `${BASE_API_URI}/qa/questions/${params.question_id}/answers`; 7 | const res = await fetch(apiURL); 8 | const data = res.ok && (await res.json()); 9 | return json(data, { status: res.status }); 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/lib/components/ShowError.svelte: -------------------------------------------------------------------------------- 1 | 7 | 8 | {#if form?.errors} 9 | 10 | {#each form?.errors as error (error.id)} 11 |

16 | {error.message} 17 |

18 | {/each} 19 | {/if} 20 | -------------------------------------------------------------------------------- /frontend/static/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/lib/components/inputs/TextArea.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | 12 | 20 |
21 | -------------------------------------------------------------------------------- /backend/src/store/general.rs: -------------------------------------------------------------------------------- 1 | use sqlx::postgres::{PgPool, PgPoolOptions}; 2 | 3 | #[derive(Clone, Debug)] 4 | pub struct Store { 5 | pub connection: PgPool, 6 | } 7 | 8 | impl Store { 9 | pub async fn new(db_url: &str) -> Self { 10 | match PgPoolOptions::new() 11 | .max_connections(8) 12 | .connect(db_url) 13 | .await 14 | { 15 | Ok(pool) => Store { connection: pool }, 16 | Err(e) => { 17 | panic!("Couldn't establish DB connection! Error: {}", e) 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/routes/+layout.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 | 18 | 19 | 20 |