Skip to content

Commit

Permalink
feat(): huge refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
Christiantyemele committed Jan 3, 2025
1 parent bb3503a commit b59150d
Show file tree
Hide file tree
Showing 18 changed files with 219 additions and 130 deletions.
4 changes: 3 additions & 1 deletion backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
target/
**/target
**/target
ta-lib/
ta-lib-0.4.0-src.tar.gz
2 changes: 1 addition & 1 deletion backend/.env
Original file line number Diff line number Diff line change
@@ -1 +1 @@
DATABASE_URL=postgres://postgres:mysecretpassword@localhost:5433/newsletter
DATABASE_URL=postgres://postgres:mysecretpassword@172.17.0.2:5432/newsletterdb
2 changes: 2 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
/target
ta-lib/
ta-lib-0.4.0-src.tar.gz
2 changes: 1 addition & 1 deletion backend/diesel.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ file = "src/database/schema.rs"
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]

[migrations_directory]
dir = "/home/christian/Videos/Newsletter/src/database"
dir = "./src/database"
18 changes: 11 additions & 7 deletions backend/src/auth/authentication.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,20 @@ pub async fn auth_verify_otp(
otp: String,
conn: &mut Database,
email_addr: String,
) -> Result<bool, SubscriptionError> {
) -> Result<(), SubscriptionError> {
let totp = Otp::new();
let result = totp
.check_current(&otp)
.map_err(|_| SubscriptionError::InternalError)?;
match subscribe(conn, email_addr)
.await
.map_err(|_| SubscriptionError::DatabaseError)
{
Ok(_) => Ok(result),
Err(e) => Err(e),
if result {
match subscribe(conn, email_addr)
.await
.map_err(|_| SubscriptionError::DatabaseError)
{
Ok(_) => Ok(()),
Err(e) => Err(e),
}
} else {
Err(SubscriptionError::OtpError)
}
}
10 changes: 6 additions & 4 deletions backend/src/auth/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ pub enum SubscriptionError {
#[error("Error storing subscruber")]
DatabaseError,
#[error("could not send otp")]
MailError
MailError,
#[error("Invalid OTP")]
OtpError
}
impl SubscriptionError {
/// Converts the error to an axum JSON representation.
pub fn json(&self) -> Json<Value> {
Json(json!({
"error": self.to_string()
}))
Json({
json!(self.to_string())
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.

DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
DROP FUNCTION IF EXISTS diesel_set_updated_at();
36 changes: 36 additions & 0 deletions backend/src/database/00000000000000_diesel_initial_setup/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.




-- Sets up a trigger for the given table to automatically set a column called
-- `updated_at` whenever the row is modified (unless `updated_at` was included
-- in the modified columns)
--
-- # Example
--
-- ```sql
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
--
-- SELECT diesel_manage_updated_at('users');
-- ```
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
BEGIN
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
BEGIN
IF (
NEW IS DISTINCT FROM OLD AND
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
) THEN
NEW.updated_at := current_timestamp;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
4 changes: 2 additions & 2 deletions backend/src/database/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ impl Otp {
Algorithm::SHA1,
6,
1,
20,
30,
Secret::Encoded("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ".to_string())
.to_bytes()
.unwrap(),
Expand All @@ -30,7 +30,7 @@ impl Otp {
Algorithm::SHA1,
6,
1,
20,
30,
Secret::Encoded("KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ".to_string())
.to_bytes()
.unwrap(),
Expand Down
7 changes: 3 additions & 4 deletions backend/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use std::sync::{Arc, Mutex};
use axum::{
http::Method,
response::IntoResponse,
routing::{get, post},
Extension, Router,
Expand All @@ -10,6 +8,7 @@ use newsletter::{
database::connection::establish_connection,
web::subscribe::{post_subscribe, post_verify_email, EmailOtp},
};
use std::sync::{Arc, Mutex};
use tokio::net::TcpListener;
use tower::ServiceBuilder;
use tower_http::{
Expand Down Expand Up @@ -37,8 +36,8 @@ async fn main() {

let email = Arc::new(Mutex::new(String::new()));
let code = EmailOtp { code: otp };
let port = 80809;
let socket = format!("0.0.0.0:{}",port);
let port = 8000;
let socket = format!("0.0.0.0:{}", port);
let listener = TcpListener::bind(socket).await.unwrap();
let router = Router::new()
.route("/", get(welcome))
Expand Down
2 changes: 1 addition & 1 deletion backend/src/web/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ use axum::{http::{Response, StatusCode}, response::IntoResponse};
pub fn error_page(e: &dyn std::error::Error) -> impl IntoResponse {
Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(format!("error: {}", e))
.body(format!("{}", e))
.unwrap()
}
2 changes: 1 addition & 1 deletion backend/src/web/subscribe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@ pub async fn post_verify_email(
let email = email.0.lock().unwrap().clone();
match auth_verify_otp(otp.code.clone(), &mut database, email).await {
Ok(_) => StatusCode::ACCEPTED.into_response(),
Err(e) => e.json().into_response(),
Err(e) => error_page(&e).into_response(),
}
}
18 changes: 9 additions & 9 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
version: '3.8' # Version of Docker Compose
version: '3.8'
services:
postgres:
image: postgres:latest
container_name: newsletter
container_name: newsletter2
ports:
- "5434:5432" # Host Port:Container Port
- "5432:5432"
environment:
POSTGRES_PASSWORD: mysecretpassword

backend:
build:
context: ./backend # Path to the backend Dockerfile
image: newsletter_k8s_backend
context: ./backend
image: newsletter_backend
container_name: backend
ports:
- "8000:8000" # Adjust if your backend uses a different port
- "8000:8000"
environment:
DATABASE_URL: postgres://postgres:mysecretpassword@localhost:5433/newsletter
DATABASE_URL: postgres://postgres:mysecretpassword@localhost:5432/newsletter
depends_on:
- postgres

frontend:
build:
context: ./frontend # Path to the frontend Dockerfile
image: newsletter_k8s_frontend
image: newsletter_frontend
container_name: frontend
ports:
- "8080:80" # Adjust if your frontend uses a different port
- "5173:5173" # Adjust if your frontend uses a different port
depends_on:
- backend

Expand Down
83 changes: 51 additions & 32 deletions frontend/src/components/pinform.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
import { useFormik } from 'formik';

import OtpInput from 'formik-otp-input';
import { useState } from 'react';
import { Alert } from './response';
import { useNavigate } from 'react-router-dom';



// CSS Styles, adjust according to your needs
const formStyle: React.CSSProperties = {
display: 'flex',
flexDirection: 'column',
Expand All @@ -32,16 +33,18 @@ const submitButtonStyle = {
marginTop: '20px',
};

// Form component

const OtpForm = () => {
const navigate = useNavigate();
const [alertMessage, setAlertMessage] = useState<string | null>(null);
const formik = useFormik({
initialValues: {
otp: '',
// ... other form fields if you wish

},
onSubmit: (values) => {
fetch('http://localhost:8000/verify_otp', {

fetch('http://0.0.0.0:8000/verify_otp', {
method: 'POST',
headers: {

Expand All @@ -54,37 +57,53 @@ const OtpForm = () => {

})
})
.then(async (response) => {
if (!response.ok) {
const message = await response.text();
setAlertMessage(message);
throw new Error(message);
}
return response;
})
.then(() => navigate('/'))
.catch((error) => {
setAlertMessage(error.message);
});
},
});

return (
<form style={formStyle} onSubmit={formik.handleSubmit}>
<OtpInput
length={6}
value={formik.values.otp}
inputType={"numeric"} // Default is numeric. Options are numeric, alphabetic or alphanumeric
autoFocus={true} // Default is true. Will auto-focus first digit if true
autoSubmit={true} // Default is true. Will auto-submit form onFullFill
onBlur={formik.handleBlur} // Formik handler, used to handle onBlur events
onChange={formik.handleChange} // Formik handler, used to handle change events
onFullFill={formik.handleSubmit} // Formik handler, used to handle autoSubmit
setFieldError={formik.setFieldError} // Formik handler, used to handle error rendering
setFieldTouched={formik.setFieldTouched}
// ... other props you can pass
highlightColor={'#4caf50'}
// textColor={'#FFFFFF'}
backgroundColor='gray'
// borderColor={'#FFFFFF'}
// ... override any pre-existing styles if required
// style={{
// 'backgroundColor': '#ffc300'
// }}
/>
{formik.errors.otp && formik.touched.otp && (
<div style={errorTextStyle}>{formik.errors.otp}</div>
<>
{alertMessage && (
<Alert
message={alertMessage}
onClose={() => setAlertMessage(null)}
/>
)}
<button type="submit" style={submitButtonStyle} >Submit</button>
</form>
<form style={formStyle} onSubmit={formik.handleSubmit}>
<OtpInput
length={6}
value={formik.values.otp}
inputType={"numeric"}
autoFocus={true}
autoSubmit={true}
onBlur={formik.handleBlur}
onChange={formik.handleChange}
onFullFill={formik.handleSubmit}
setFieldError={formik.setFieldError}
setFieldTouched={formik.setFieldTouched}

highlightColor={'#4caf50'}

backgroundColor='gray'

/>
{formik.errors.otp && formik.touched.otp && (
<div style={errorTextStyle}>{formik.errors.otp}</div>
)}
<button type="submit" style={submitButtonStyle} >Submit</button>
</form>
</>
);
};

Expand Down
34 changes: 21 additions & 13 deletions frontend/src/components/response.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { useEffect } from "react";

export default function Response(res: string) {
return (
<>
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
<strong className="font-bold">Holy smokes!</strong>
<span className="block sm:inline">{res}</span>
<span className="absolute top-0 bottom-0 right-0 px-4 py-3">
<svg className="fill-current h-6 w-6 text-red-500" role="button" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><title>Close</title><path d="M14.348 14.849a1.2 1.2 0 0 1-1.697 0L10 11.819l-2.651 3.029a1.2 1.2 0 1 1-1.697-1.697l2.758-3.15-2.759-3.152a1.2 1.2 0 1 1 1.697-1.697L10 8.183l2.651-3.031a1.2 1.2 0 1 1 1.697 1.697l-2.758 3.152 2.758 3.15a1.2 1.2 0 0 1 0 1.698z" /></svg>
</span>
</div>
</>
)
}
export function Alert({ message, onClose }: { message: string; onClose: () => void }) {
useEffect(() => {
const timer = setTimeout(() => {
onClose(); // Automatically dismiss after 5 seconds
}, 5000);
return () => clearTimeout(timer);
}, [onClose]);

return (
<div
role="alert"
className="fixed top-0 left-1/2 transform -translate-x-1/2 mt-4 z-50
bg-accentcolor text-white px-4 py-3 rounded shadow-lg animate-slide-down"
>
<div className="flex justify-between items-center">
<span className="ml-2">{message}</span>
</div>
</div>
);
}
Loading

0 comments on commit b59150d

Please sign in to comment.