Skip to content
Open
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
47 changes: 29 additions & 18 deletions API/gmail.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@
from email.message import EmailMessage
from fastapi import APIRouter, HTTPException, Body
from models.EmailRequest import EmailRequest

"""
Email backend
Function: send alert email to user's address (containing plane and event data)
TEST: send fixed alert email to user's address
params:
- request
"""
router = APIRouter()


# Main send email function (TODO)
@router.post("/send-email")
def send_email():
mail_host = "smtp.gmail.com" # mail manager host
port = 587 # standard SMTP submission port (STARTTLS)

recipient_email = "<EMAIL>" # email address that will receive mail
sender_email = "<EMAIL>" # email address that will send mail
sender_password = os.getenv("GMAIL_APP_PASSWORD") # gmail app password
recipient_email = "<EMAIL>" # email address that will receive mail

# TODO: adapt message to event parameters (plane id, coordinates, time, etc)
# compose email
msg = EmailMessage() # init email obj
msg["Subject"] = "Planium : A plane is flying near the Moon 🌙" # email subject
Expand All @@ -24,26 +28,33 @@ def send_email():
msg.set_content("A plane is about to fly near the moon.") # email content

# send email
with smtplib.SMTP(mail_host, port) as smtp:
smtp.starttls()
smtp.login(sender_email, sender_password) # login with user credentials
smtp.send_message(msg)
try:
with smtplib.SMTP("smtp.gmail.com", 587) as smtp:
smtp.starttls()
smtp.login(sender_email, sender_password)
smtp.send_message(msg)
return {"success": True}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

# send test email
# TEST : send fixed email from private address
# TEST PASSED SUCCESSFULLY
@router.post("/send-test-email")
def send_test_email(request: EmailRequest):
sender_email = "zzabcmail123@gmail.com"
sender_password = os.getenv("GMAIL_APP_PASSWORD")
recipient_email = request.user_email # email address that will receive mail
sender_email = "zzabcmail123@gmail.com" # email address that will send mail
sender_password = os.getenv("GMAIL_APP_PASSWORD") # gmail app password

if not sender_password:
raise HTTPException(status_code=500, detail="GMAIL_APP_PASSWORD not set")

msg = EmailMessage()
msg["Subject"] = "Planium : A plane is flying near the Moon 🌙"
msg["From"] = sender_email
msg["To"] = request.user_email
msg.set_content("A plane is about to fly near the moon.")
msg = EmailMessage() # init email obj
msg["Subject"] = "Planium : A plane is flying near the Moon 🌙" # email subject
msg["From"] = sender_email # sender
msg["To"] = recipient_email # recipient
msg.set_content("A plane is about to fly near the moon.") # email content

# send email
try:
with smtplib.SMTP("smtp.gmail.com", 587) as smtp:
smtp.starttls()
Expand Down
55 changes: 35 additions & 20 deletions src/components/MoonMiniViewer.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
<script setup>
import {computed, onMounted, onUnmounted, ref, watch} from 'vue';
import {loadPlanes, updatePlanes} from "@/utils/scene.js";
// import {loadPlanes, updatePlanes} from "@/utils/scene.js"; // plane api functions (commented methods to save GPU power)

/*
Moon Mini Viewer summary description:
1 - Displays the Moon in a dedicated Cesium viewer (separate to "Viewer.vue").
2 - Receives data from main viewer (mainViewer, Cesium, moonPos, planes) to ensure coherence between viewers.
3 - Camera direction is locked to the Moon.
4 - Camera position is the same as main Viewer.
5 - Yellow reticle around Moon is fixed (doesn't track Moon's position, Viewer does automatically)
6 - Plane display is currently disabled (double viewer render freezes web app because of GPU overload)
*/

// data from Viewer.vue
const props = defineProps({
Expand All @@ -10,13 +20,16 @@ const props = defineProps({
planes: Array // pass plane data
});

// initialize containers
const miniViewerContainer = ref(null);
let miniViewer = null;
let syncLayers = null;

// Moon constants
const moonRadius = 1737400 // meters
const moonWidthMultiplier = ref(6); // zoom width (Moon's diameters, default: 6 Moons)

// reticle responsive style (reacts to Moon's size)
const reticleStyle = computed(() => { // adjust reticle to moon's size
const size = (400 / moonWidthMultiplier.value) + 2; // proportion (ex. 2 Moons wide, reticle = 202x202px)
return {
Expand All @@ -25,35 +38,37 @@ const reticleStyle = computed(() => { // adjust reticle to moon's size
};
});

// point miniViewer's camera to Moon reactively
// points miniViewer's camera to Moon reactively
function updateMiniView() {
// safety checks
if (!props.moonPos || !miniViewer || !miniViewer.scene || miniViewer.isDestroyed() || !props.mainViewer.camera.position) return

// props storage for reference
const Cesium = props.Cesium
const mainCamera = props.mainViewer.camera

// calculate direction vector from main camera to Moon
const moonDirection = Cesium.Cartesian3.subtract(props.moonPos, mainCamera.position, new Cesium.Cartesian3());
const distanceToMoon = Cesium.Cartesian3.magnitude(moonDirection);
Cesium.Cartesian3.normalize(moonDirection, moonDirection);
const moonDirection = Cesium.Cartesian3.subtract(props.moonPos, mainCamera.position, new Cesium.Cartesian3()); // get direction subtracting cartesian positions
const distanceToMoon = Cesium.Cartesian3.magnitude(moonDirection); // get distance (vector's length)
Cesium.Cartesian3.normalize(moonDirection, moonDirection); // get unit vector (normalize vector)

// set miniViewer's camera to same position as mainCamera but pointing the Moon
// set miniViewer's camera to same position as mainCamera, but pointing the Moon
miniViewer.camera.setView({
destination: mainCamera.position,
destination: mainCamera.position, // original main viewer's camera cartesian coordinates
orientation: {
direction: moonDirection,
up: mainCamera.up
direction: moonDirection, // calculated vector pointing to the Moon
up: mainCamera.up // original main viewer camera's up vector
}
});

// sync clock to mainViewer's
// sync clock to mainViewer's for coherence
miniViewer.clock.currentTime = props.mainViewer.clock.currentTime;

// calculate Moon's angular width
// help of GEMINI
// calculate Moon's angular width :
// (help of GEMINI)
// opp side: moonRadius, adj side: distanceToMoon, angle: half Moon
// tan = opp/adj, angle = inverse tan (atan)
// multiply by 2 to get full width
// 1. tan = opp/adj, angle = atan(opp/adj), atan is inverse tan
// 2. multiply by 2 to get full width
const moonAngularSize = 2 * Math.atan(moonRadius / distanceToMoon)
miniViewer.camera.frustum.fov = moonAngularSize * moonWidthMultiplier.value // adjust "Field of View" to zoom
}
Expand All @@ -64,9 +79,9 @@ onMounted(async () => {
// initialize miniViewer
miniViewer = new Cesium.Viewer(miniViewerContainer.value, {
sceneMode: Cesium.SceneMode.SCENE3D,
terrainProvider: mainViewer.terrainProvider,
terrainProvider: mainViewer.terrainProvider, // load terrains from main Viewer
creditContainer: document.createElement('div'), // hide credits
// hide default UI
// hide default UI widgets inside viewer
animation: false, timeline: false, geocoder: false, homeButton: false,
infoBox: false, selectionIndicator: false, navigationHelpButton: false,
sceneModePicker: false, fullscreenButton: false, baseLayerPicker: false,
Expand Down Expand Up @@ -103,11 +118,11 @@ onMounted(async () => {

miniViewer.clock.currentTime = mainViewer.clock.currentTime; // sync clock
mainViewer.camera.changed.addEventListener(updateMiniView) // sync camera
// await loadPlanes(miniViewer) // load plane assets
// await loadPlanes(miniViewer) // load plane assets (commented to save GPU power, doubles consumption)
})

onUnmounted(() => {
// help of GEMINI
// help of GEMINI: important prevention for security
if (miniViewer) {
// remove listeners to prevent memory leaks
props.mainViewer.camera.changed.removeEventListener(updateMiniView);
Expand All @@ -122,7 +137,7 @@ onUnmounted(() => {
watch(moonWidthMultiplier, updateMiniView);
// react to Moon position
watch(() => props.moonPos, updateMiniView, { deep: true });
// react to planes data
// react to planes data (commented to save GPU power, doubles consumption)
// watch(() => props.planes, (newData) => {
// if (miniViewer) updatePlanes(miniViewer, newData);
// }, { deep: true });
Expand Down Expand Up @@ -153,7 +168,7 @@ watch(() => props.moonPos, updateMiniView, { deep: true });
</template>

<style scoped>
/* absolute positions for containers and reticle */
/* absolute css positions for containers and reticle */
.telescope-container {
position: absolute;
bottom: 24px;
Expand Down
2 changes: 1 addition & 1 deletion src/components/primitive/Moon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { ref, onBeforeUnmount, computed } from "vue";

/*
This component defines Moon's parameters and places it to real location in real time.
Moon.vue defines Moon's label and point of reference, and places them to its real location in real time.
*/

// reactive references for Moon's visual appearance and position
Expand Down
7 changes: 4 additions & 3 deletions src/utils/mail.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
// Uses backend api route to post email data (subscription check and user's address)
export async function sendTestEmail(emailCheck, user_email) {
if (!emailCheck) {
alert("Please check the box to receive email notifications.");
return;
}
if (!user_email) {
alert("Please enter a valid email address.");
alert("Please enter a valid email address."); // Basic email security (TODO: improve email recognition)
return;
}

try{
const res = await fetch("http://localhost:8080/api/send-test-email", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ user_email: user_email }),
body: JSON.stringify({ user_email: user_email }), // POST user's address to email route
});

if (!res.ok) throw new Error("Failed to send test email");
if (!res.ok) throw new Error("Failed to send test email"); // check res state

alert("Test email sent successfully.");
} catch (error) {
Expand Down