├── backend ├── Dockerfile ├── .dockerignore ├── .gitignore ├── endpoints │ ├── __init__.py │ ├── sources.py │ ├── telescopes.py │ ├── positions.py │ ├── users.py │ ├── readings.py │ └── commands.py ├── tests │ ├── __init__.py │ └── test_database.py ├── classes │ ├── __init__.py │ ├── source.py │ ├── position.py │ ├── telescope.py │ ├── user.py │ ├── command.py │ ├── reading.py │ ├── Ifastapi.py │ └── database.py ├── requirements.txt └── main.py ├── frontend ├── .dockerignore ├── src │ ├── app │ │ ├── app.component.css │ │ ├── components │ │ │ ├── cam │ │ │ │ ├── cam.component.css │ │ │ │ ├── cam.component.spec.ts │ │ │ │ ├── cam.component.html │ │ │ │ └── cam.component.ts │ │ │ ├── home │ │ │ │ ├── home.component.css │ │ │ │ ├── home.component.html │ │ │ │ ├── home.component.ts │ │ │ │ └── home.component.spec.ts │ │ │ ├── users │ │ │ │ ├── users.component.css │ │ │ │ ├── users.component.html │ │ │ │ ├── users.component.ts │ │ │ │ └── users.component.spec.ts │ │ │ ├── sign-in │ │ │ │ ├── sign-in.component.css │ │ │ │ ├── sign-in.component.spec.ts │ │ │ │ ├── sign-in.component.ts │ │ │ │ └── sign-in.component.html │ │ │ ├── sign-up │ │ │ │ ├── sign-up.component.css │ │ │ │ ├── sign-up.component.spec.ts │ │ │ │ ├── sign-up.component.ts │ │ │ │ └── sign-up.component.html │ │ │ ├── sources │ │ │ │ ├── sources.component.css │ │ │ │ ├── sources.component.html │ │ │ │ ├── sources.component.ts │ │ │ │ └── sources.component.spec.ts │ │ │ ├── app-layout │ │ │ │ ├── app-layout.component.css │ │ │ │ ├── app-layout.component.html │ │ │ │ ├── app-layout.component.ts │ │ │ │ └── app-layout.component.spec.ts │ │ │ ├── commands │ │ │ │ ├── commands.component.css │ │ │ │ ├── commands.component.spec.ts │ │ │ │ ├── commands.component.html │ │ │ │ └── commands.component.ts │ │ │ ├── readings │ │ │ │ ├── readings.component.css │ │ │ │ ├── readings.component.spec.ts │ │ │ │ ├── readings.component.html │ │ │ │ └── readings.component.ts │ │ │ ├── telescopes │ │ │ │ ├── telescopes.component.css │ │ │ │ ├── telescopes.component.spec.ts │ │ │ │ ├── telescopes.component.html │ │ │ │ └── telescopes.component.ts │ │ │ ├── user-details │ │ │ │ ├── user-details.component.css │ │ │ │ ├── user-details.component.html │ │ │ │ ├── user-details.component.ts │ │ │ │ └── user-details.component.spec.ts │ │ │ ├── source-details │ │ │ │ ├── source-details.component.css │ │ │ │ ├── source-details.component.html │ │ │ │ ├── source-details.component.ts │ │ │ │ └── source-details.component.spec.ts │ │ │ ├── users-details │ │ │ │ ├── users-details.component.css │ │ │ │ ├── users-details.component.html │ │ │ │ ├── users-details.component.ts │ │ │ │ └── users-details.component.spec.ts │ │ │ ├── telescope-details │ │ │ │ ├── telescope-details.component.css │ │ │ │ ├── telescope-details.component.html │ │ │ │ ├── telescope-details.component.ts │ │ │ │ └── telescope-details.component.spec.ts │ │ │ ├── control-and-monitoring │ │ │ │ ├── control-and-monitoring.component.css │ │ │ │ ├── control-and-monitoring.component.html │ │ │ │ ├── control-and-monitoring.component.ts │ │ │ │ └── control-and-monitoring.component.spec.ts │ │ │ └── navbar │ │ │ │ ├── navbar.component.css │ │ │ │ ├── navbar.component.spec.ts │ │ │ │ ├── navbar.component.ts │ │ │ │ └── navbar.component.html │ │ ├── app.component.html │ │ ├── interfaces │ │ │ ├── create-telescope.ts │ │ │ ├── user.ts │ │ │ ├── telescope.ts │ │ │ ├── command.ts │ │ │ ├── reading.ts │ │ │ ├── create-command.ts │ │ │ └── create-reading.ts │ │ ├── guards │ │ │ ├── user.guard.ts │ │ │ └── user.guard.spec.ts │ │ ├── app.component.ts │ │ ├── app.config.ts │ │ ├── services │ │ │ ├── user.service.spec.ts │ │ │ ├── command.service.spec.ts │ │ │ ├── reading.service.spec.ts │ │ │ ├── telescope.service.spec.ts │ │ │ ├── command.service.ts │ │ │ ├── reading.service.ts │ │ │ ├── telescope.service.ts │ │ │ └── user.service.ts │ │ ├── signal.ts │ │ ├── app.component.spec.ts │ │ └── app.routes.ts │ ├── styles.css │ ├── environments │ │ ├── environment.ts │ │ └── environment.development.ts │ ├── main.ts │ └── index.html ├── public │ ├── favicon.ico │ └── assets │ │ ├── meerkat.avif │ │ ├── sarao-logo.png │ │ └── sarao-evening-observation.jpg ├── .editorconfig ├── tsconfig.app.json ├── tsconfig.spec.json ├── .gitignore ├── Dockerfile ├── tsconfig.json ├── package.json ├── README.md └── angular.json ├── .gitignore ├── README.md ├── docker-compose.yaml └── design ├── architecture.drawio └── database.drawio /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/.dockerignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv* 2 | 3 | __pycache__* -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | *.db 2 | 3 | *.env -------------------------------------------------------------------------------- /frontend/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/endpoints/__init__.py: -------------------------------------------------------------------------------- 1 | from .users import * -------------------------------------------------------------------------------- /backend/tests/__init__.py: -------------------------------------------------------------------------------- 1 | from .test_database import * -------------------------------------------------------------------------------- /frontend/src/app/components/cam/cam.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/components/home/home.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/components/users/users.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/components/sign-in/sign-in.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/components/sign-up/sign-up.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/components/sources/sources.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/components/app-layout/app-layout.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/components/commands/commands.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/components/readings/readings.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/components/telescopes/telescopes.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/components/user-details/user-details.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/components/source-details/source-details.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/components/users-details/users-details.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/components/telescope-details/telescope-details.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/components/users/users.component.html: -------------------------------------------------------------------------------- 1 |

users works!

2 | -------------------------------------------------------------------------------- /backend/classes/__init__.py: -------------------------------------------------------------------------------- 1 | from .user import User 2 | from .database import Database -------------------------------------------------------------------------------- /frontend/src/app/components/control-and-monitoring/control-and-monitoring.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/components/sources/sources.component.html: -------------------------------------------------------------------------------- 1 |

sources works!

2 | -------------------------------------------------------------------------------- /frontend/src/app/components/user-details/user-details.component.html: -------------------------------------------------------------------------------- 1 |

user-details works!

2 | -------------------------------------------------------------------------------- /frontend/src/app/components/users-details/users-details.component.html: -------------------------------------------------------------------------------- 1 |

users-details works!

2 | -------------------------------------------------------------------------------- /frontend/src/app/components/source-details/source-details.component.html: -------------------------------------------------------------------------------- 1 |

source-details works!

2 | -------------------------------------------------------------------------------- /frontend/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ska-sa/astt-2024-software/master/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/src/app/components/home/home.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /frontend/src/app/components/telescope-details/telescope-details.component.html: -------------------------------------------------------------------------------- 1 |

telescope-details works!

2 | -------------------------------------------------------------------------------- /frontend/src/app/components/app-layout/app-layout.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /frontend/public/assets/meerkat.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ska-sa/astt-2024-software/master/frontend/public/assets/meerkat.avif -------------------------------------------------------------------------------- /frontend/src/app/components/control-and-monitoring/control-and-monitoring.component.html: -------------------------------------------------------------------------------- 1 |

control-and-monitoring works!

2 | -------------------------------------------------------------------------------- /frontend/public/assets/sarao-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ska-sa/astt-2024-software/master/frontend/public/assets/sarao-logo.png -------------------------------------------------------------------------------- /frontend/src/app/interfaces/create-telescope.ts: -------------------------------------------------------------------------------- 1 | export interface CreateTelescope { 2 | name: string; 3 | health_status: number; 4 | } -------------------------------------------------------------------------------- /frontend/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | host: "localhost", 4 | port: 8000 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/public/assets/sarao-evening-observation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ska-sa/astt-2024-software/master/frontend/public/assets/sarao-evening-observation.jpg -------------------------------------------------------------------------------- /frontend/src/environments/environment.development.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: false, 3 | host: "localhost", 4 | port: 8000 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/src/app/interfaces/user.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | id: number; 3 | email_address: string; 4 | password: string; 5 | create_at: Date; 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ASTT Software 2 | 3 | ## Description 4 | 5 | ## Getting Started 6 | 7 | ## Design Diagrams 8 | 9 | ## Testing 10 | 11 | ## Conclusion 12 | -------------------------------------------------------------------------------- /frontend/src/app/interfaces/telescope.ts: -------------------------------------------------------------------------------- 1 | export interface Telescope { 2 | id: number; 3 | name: string; 4 | health_status: number; 5 | created_at: Date; 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/app/interfaces/command.ts: -------------------------------------------------------------------------------- 1 | import { CreateCommand } from "./create-command"; 2 | 3 | export interface Command extends CreateCommand { 4 | id: number; 5 | created_at: Date; 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/app/interfaces/reading.ts: -------------------------------------------------------------------------------- 1 | import { CreateReading } from "./create-reading"; 2 | 3 | export interface Reading extends CreateReading { 4 | id: number; 5 | created_at: Date; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /frontend/src/app/interfaces/create-command.ts: -------------------------------------------------------------------------------- 1 | export interface CreateCommand { 2 | user_id: number; 3 | telescope_id: number; 4 | target_az_angle: number; 5 | target_el_angle: number; 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/app/interfaces/create-reading.ts: -------------------------------------------------------------------------------- 1 | export interface CreateReading { 2 | telescope_id: number; 3 | az_angle: number; 4 | el_angle: number; 5 | health_status: string; 6 | movement_status: string; 7 | } -------------------------------------------------------------------------------- /frontend/src/app/guards/user.guard.ts: -------------------------------------------------------------------------------- 1 | import { CanActivateFn } from '@angular/router'; 2 | import { getUser } from '../signal'; 3 | 4 | export const userGuard: CanActivateFn = (route, state) => { 5 | return getUser() !== null; 6 | }; 7 | -------------------------------------------------------------------------------- /backend/classes/source.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from pydantic import BaseModel 3 | 4 | class CreateSource(BaseModel): 5 | name: str 6 | 7 | class Source(CreateSource): 8 | id: int 9 | 10 | class Config: 11 | from_attributes = True -------------------------------------------------------------------------------- /frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrapApplication } from '@angular/platform-browser'; 2 | import { appConfig } from './app/app.config'; 3 | import { AppComponent } from './app/app.component'; 4 | 5 | bootstrapApplication(AppComponent, appConfig) 6 | .catch((err) => console.error(err)); 7 | -------------------------------------------------------------------------------- /frontend/src/app/components/users/users.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-users', 5 | imports: [], 6 | templateUrl: './users.component.html', 7 | styleUrl: './users.component.css' 8 | }) 9 | export class UsersComponent { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /backend/classes/position.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from pydantic import BaseModel 3 | 4 | class CreatePosition(BaseModel): 5 | datetime : datetime 6 | 7 | class Position(CreatePosition): 8 | id: int 9 | created_at: datetime 10 | 11 | class Config: 12 | from_attribute = True -------------------------------------------------------------------------------- /frontend/src/app/components/sources/sources.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-sources', 5 | imports: [], 6 | templateUrl: './sources.component.html', 7 | styleUrl: './sources.component.css' 8 | }) 9 | export class SourcesComponent { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /backend/classes/telescope.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from pydantic import BaseModel 3 | 4 | class CreateTelescope(BaseModel): 5 | name: str 6 | health_status: int 7 | 8 | class Telescope(CreateTelescope): 9 | id: int 10 | created_at: datetime 11 | 12 | class Config: 13 | from_attributes = True -------------------------------------------------------------------------------- /frontend/src/app/components/navbar/navbar.component.css: -------------------------------------------------------------------------------- 1 | .nav-link { 2 | cursor: pointer; 3 | transition: transform 0.2s, font-weight 0.2s; 4 | margin-left: 20px; 5 | color: #0d6efd; 6 | } 7 | .nav-link:hover { 8 | transform: scale(1.1); 9 | font-weight: bolder; 10 | } 11 | 12 | .nav-link.active { 13 | font-weight: bold; 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/app/components/user-details/user-details.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-user-details', 5 | imports: [], 6 | templateUrl: './user-details.component.html', 7 | styleUrl: './user-details.component.css' 8 | }) 9 | export class UserDetailsComponent { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/app/components/users-details/users-details.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-users-details', 5 | imports: [], 6 | templateUrl: './users-details.component.html', 7 | styleUrl: './users-details.component.css' 8 | }) 9 | export class UsersDetailsComponent { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /backend/classes/user.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from datetime import datetime 3 | from pydantic import BaseModel 4 | 5 | class CreateUser(BaseModel): 6 | email_address: str 7 | password: str 8 | 9 | class User(CreateUser): 10 | id: int 11 | created_at: datetime 12 | 13 | class Config: 14 | from_attributes = True 15 | -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | annotated-types==0.7.0 2 | anyio==4.9.0 3 | click==8.1.8 4 | exceptiongroup==1.2.2 5 | fastapi==0.115.12 6 | h11==0.16.0 7 | idna==3.10 8 | pydantic==2.11.4 9 | pydantic_core==2.33.2 10 | python-dotenv==1.1.0 11 | sniffio==1.3.1 12 | starlette==0.46.2 13 | typing-inspection==0.4.0 14 | typing_extensions==4.13.2 15 | uvicorn==0.34.2 16 | -------------------------------------------------------------------------------- /frontend/src/app/components/source-details/source-details.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-source-details', 5 | imports: [], 6 | templateUrl: './source-details.component.html', 7 | styleUrl: './source-details.component.css' 8 | }) 9 | export class SourceDetailsComponent { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Frontend 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/app/components/telescope-details/telescope-details.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-telescope-details', 5 | imports: [], 6 | templateUrl: './telescope-details.component.html', 7 | styleUrl: './telescope-details.component.css' 8 | }) 9 | export class TelescopeDetailsComponent { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { RouterOutlet } from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | imports: [ 7 | RouterOutlet, 8 | ], 9 | templateUrl: './app.component.html', 10 | styleUrl: './app.component.css' 11 | }) 12 | export class AppComponent { 13 | title = 'frontend'; 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/app/components/control-and-monitoring/control-and-monitoring.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-control-and-monitoring', 5 | imports: [], 6 | templateUrl: './control-and-monitoring.component.html', 7 | styleUrl: './control-and-monitoring.component.css' 8 | }) 9 | export class ControlAndMonitoringComponent { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | ij_typescript_use_double_quotes = false 14 | 15 | [*.md] 16 | max_line_length = off 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /backend/classes/command.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from pydantic import BaseModel 3 | from datetime import datetime 4 | 5 | class CreateCommand(BaseModel): 6 | user_id: int 7 | telescope_id: int 8 | target_az_angle: float 9 | target_el_angle: float 10 | 11 | class Command(CreateCommand): 12 | id: int 13 | created_at: datetime 14 | 15 | class Config: 16 | from_attributes = True 17 | -------------------------------------------------------------------------------- /frontend/src/app/app.config.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; 2 | import { provideRouter } from '@angular/router'; 3 | 4 | import { routes } from './app.routes'; 5 | import { provideHttpClient } from '@angular/common/http'; 6 | 7 | export const appConfig: ApplicationConfig = { 8 | providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideHttpClient()] 9 | }; 10 | -------------------------------------------------------------------------------- /frontend/src/app/services/user.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { UserService } from './user.service'; 4 | 5 | describe('UserService', () => { 6 | let service: UserService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(UserService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /backend/classes/reading.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from datetime import datetime 3 | from pydantic import BaseModel 4 | 5 | class CreateReading(BaseModel): 6 | telescope_id: int 7 | az_angle: float 8 | el_angle: float 9 | health_status: str 10 | movement_status: str 11 | 12 | class Reading(CreateReading): 13 | id: int 14 | created_at: datetime 15 | 16 | class Config: 17 | from_attribute = True 18 | 19 | 20 | -------------------------------------------------------------------------------- /frontend/src/app/services/command.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { CommandService } from './command.service'; 4 | 5 | describe('CommandService', () => { 6 | let service: CommandService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(CommandService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /frontend/src/app/services/reading.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { ReadingService } from './reading.service'; 4 | 5 | describe('ReadingService', () => { 6 | let service: ReadingService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(ReadingService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /frontend/src/app/components/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NavbarComponent } from '../navbar/navbar.component'; 3 | import { TelescopesComponent } from '../telescopes/telescopes.component'; 4 | 5 | @Component({ 6 | selector: 'app-home', 7 | imports: [NavbarComponent, TelescopesComponent], 8 | templateUrl: './home.component.html', 9 | styleUrl: './home.component.css' 10 | }) 11 | export class HomeComponent { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/app/services/telescope.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { TelescopeService } from './telescope.service'; 4 | 5 | describe('TelescopeService', () => { 6 | let service: TelescopeService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(TelescopeService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /frontend/src/app/components/app-layout/app-layout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { NavbarComponent } from '../navbar/navbar.component'; 3 | import { RouterOutlet } from '@angular/router'; 4 | 5 | @Component({ 6 | selector: 'app-app-layout', 7 | imports: [ 8 | NavbarComponent, 9 | RouterOutlet 10 | ], 11 | templateUrl: './app-layout.component.html', 12 | styleUrl: './app-layout.component.css' 13 | }) 14 | export class AppLayoutComponent { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /frontend/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ 2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ 3 | { 4 | "extends": "./tsconfig.json", 5 | "compilerOptions": { 6 | "outDir": "./out-tsc/app", 7 | "types": [] 8 | }, 9 | "files": [ 10 | "src/main.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /frontend/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ 2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ 3 | { 4 | "extends": "./tsconfig.json", 5 | "compilerOptions": { 6 | "outDir": "./out-tsc/spec", 7 | "types": [ 8 | "jasmine" 9 | ] 10 | }, 11 | "include": [ 12 | "src/**/*.spec.ts", 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/app/guards/user.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { CanActivateFn } from '@angular/router'; 3 | 4 | import { userGuard } from './user.guard'; 5 | 6 | describe('userGuard', () => { 7 | const executeGuard: CanActivateFn = (...guardParameters) => 8 | TestBed.runInInjectionContext(() => userGuard(...guardParameters)); 9 | 10 | beforeEach(() => { 11 | TestBed.configureTestingModule({}); 12 | }); 13 | 14 | it('should be created', () => { 15 | expect(executeGuard).toBeTruthy(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | astt-software-backend-service: 4 | container_name: astt-software-backend-container 5 | image: astt-software-backend-image 6 | build: 7 | context: ./backend 8 | dockerfile: Dockerfile 9 | ports: 10 | - "5000:5000" 11 | astt-software-frontend-service: 12 | container_name: astt-software-frontend-contaier 13 | image: :astt-software-frontend-image 14 | build: 15 | context: ./frontend 16 | dockerfile: Dockerfile 17 | ports: 18 | - "80:4300" 19 | depends_on: 20 | - astt-software-backend-service -------------------------------------------------------------------------------- /backend/tests/test_database.py: -------------------------------------------------------------------------------- 1 | """This file is responsible for testing the insert function from database class""" 2 | from classes import Database 3 | 4 | 5 | def test_database_insert() -> None: 6 | """This function tests the functionality of the insert to database function""" 7 | db = Database() 8 | user_dict = { 9 | "id": 1, 10 | "email_address": "test@astt.com", 11 | "type": 2 12 | } 13 | 14 | assert "testing" in db.name 15 | assert db.insert("user", user_dict) 16 | 17 | self.id: int = id 18 | self.email: str = email 19 | self.password: str = password 20 | created_at: datetime = created_at -------------------------------------------------------------------------------- /frontend/src/app/components/cam/cam.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CamComponent } from './cam.component'; 4 | 5 | describe('CamComponent', () => { 6 | let component: CamComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [CamComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(CamComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /frontend/src/app/components/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | 5 | describe('HomeComponent', () => { 6 | let component: HomeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [HomeComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(HomeComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /frontend/src/app/components/users/users.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UsersComponent } from './users.component'; 4 | 5 | describe('UsersComponent', () => { 6 | let component: UsersComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [UsersComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(UsersComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /frontend/src/app/components/navbar/navbar.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { NavbarComponent } from './navbar.component'; 4 | 5 | describe('NavbarComponent', () => { 6 | let component: NavbarComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [NavbarComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(NavbarComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /frontend/src/app/components/sign-in/sign-in.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SignInComponent } from './sign-in.component'; 4 | 5 | describe('SignInComponent', () => { 6 | let component: SignInComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [SignInComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(SignInComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /frontend/src/app/components/sign-up/sign-up.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SignUpComponent } from './sign-up.component'; 4 | 5 | describe('SignUpComponent', () => { 6 | let component: SignUpComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [SignUpComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(SignUpComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /frontend/src/app/components/sources/sources.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SourcesComponent } from './sources.component'; 4 | 5 | describe('SourcesComponent', () => { 6 | let component: SourcesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [SourcesComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(SourcesComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /frontend/src/app/components/commands/commands.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CommandsComponent } from './commands.component'; 4 | 5 | describe('CommandsComponent', () => { 6 | let component: CommandsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [CommandsComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(CommandsComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /frontend/src/app/components/readings/readings.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ReadingsComponent } from './readings.component'; 4 | 5 | describe('ReadingsComponent', () => { 6 | let component: ReadingsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ReadingsComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(ReadingsComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /frontend/src/app/components/app-layout/app-layout.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AppLayoutComponent } from './app-layout.component'; 4 | 5 | describe('AppLayoutComponent', () => { 6 | let component: AppLayoutComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [AppLayoutComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(AppLayoutComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /frontend/src/app/components/telescopes/telescopes.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TelescopesComponent } from './telescopes.component'; 4 | 5 | describe('TelescopesComponent', () => { 6 | let component: TelescopesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [TelescopesComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(TelescopesComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | *.vscode/ 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /frontend/src/app/components/user-details/user-details.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UserDetailsComponent } from './user-details.component'; 4 | 5 | describe('UserDetailsComponent', () => { 6 | let component: UserDetailsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [UserDetailsComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(UserDetailsComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /frontend/src/app/components/users-details/users-details.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { UsersDetailsComponent } from './users-details.component'; 4 | 5 | describe('UsersDetailsComponent', () => { 6 | let component: UsersDetailsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [UsersDetailsComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(UsersDetailsComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /frontend/src/app/components/source-details/source-details.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SourceDetailsComponent } from './source-details.component'; 4 | 5 | describe('SourceDetailsComponent', () => { 6 | let component: SourceDetailsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [SourceDetailsComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(SourceDetailsComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /frontend/src/app/signal.ts: -------------------------------------------------------------------------------- 1 | import { signal } from "@angular/core"; 2 | import { User } from "./interfaces/user"; 3 | 4 | export let user = signal(null); 5 | 6 | export function setUser(newUser: User | null) { 7 | if (newUser === null) { 8 | localStorage.removeItem('user'); 9 | user.set(null); 10 | return; 11 | } 12 | localStorage.setItem('user', JSON.stringify(newUser)); 13 | user.set(newUser); 14 | } 15 | 16 | export function getUser(): User | null { 17 | if (user() !== null) { 18 | return user(); 19 | } 20 | const userData = localStorage.getItem('user'); 21 | if (userData) { 22 | const parsedUser = JSON.parse(userData); 23 | user.set(parsedUser); 24 | return parsedUser; 25 | } 26 | return null; 27 | } -------------------------------------------------------------------------------- /frontend/src/app/components/telescope-details/telescope-details.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TelescopeDetailsComponent } from './telescope-details.component'; 4 | 5 | describe('TelescopeDetailsComponent', () => { 6 | let component: TelescopeDetailsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [TelescopeDetailsComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(TelescopeDetailsComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /frontend/src/app/components/readings/readings.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
IDTelescope NameHeath StatusModeMovenment StatusAzimuth AngleElevation AngleCreated At
1ASTT 2024NormalPointingStopped32.50 °67.87 °03/05/2025 15:14:30
-------------------------------------------------------------------------------- /frontend/src/app/components/control-and-monitoring/control-and-monitoring.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ControlAndMonitoringComponent } from './control-and-monitoring.component'; 4 | 5 | describe('ControlAndMonitoringComponent', () => { 6 | let component: ControlAndMonitoringComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ControlAndMonitoringComponent] 12 | }) 13 | .compileComponents(); 14 | 15 | fixture = TestBed.createComponent(ControlAndMonitoringComponent); 16 | component = fixture.componentInstance; 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should create', () => { 21 | expect(component).toBeTruthy(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | # This Dockerfile is used to build the frontend application using Node.js and Alpine Linux. 2 | FROM node:20-alpine 3 | 4 | # Set the working directory inside the container 5 | WORKDIR /app 6 | 7 | # Copy the package.json and package-lock.json files to the working directory 8 | COPY package*.json ./ 9 | # Install the application dependencies 10 | RUN npm install --legacy-peer-deps 11 | 12 | # Copy the rest of the application code to the working directory 13 | COPY . . 14 | 15 | # Build the application 16 | RUN npm run build -- --prod --output-path=/www/localhost/htdocs 17 | 18 | # Get apache2 package 19 | RUN apk add --no-cache apache2 20 | 21 | # Copy the built application to the Apache document root 22 | RUN cp -r dist/* /var/www/localhost/htdocs/ 23 | 24 | # Expose port 80 for the Apache server 25 | EXPOSE 80 26 | 27 | # Start the Apache server in the foreground 28 | CMD ["httpd", "-D", "FOREGROUND"] -------------------------------------------------------------------------------- /frontend/src/app/services/command.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpHeaders } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { environment } from '../../environments/environment'; 4 | import { Command } from '../interfaces/command'; 5 | import { CreateCommand } from '../interfaces/create-command'; 6 | import { Observable } from 'rxjs'; 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class CommandService { 12 | url: string = `http://${environment.host}:${environment.port}/api/v1/commands/`; 13 | httpHeaders: HttpHeaders = new HttpHeaders({ 14 | 'Content-Type': 'application/json' 15 | }); 16 | 17 | constructor(private httpClient: HttpClient) { } 18 | 19 | getCommands(): Observable { 20 | return this.httpClient.get(this.url, { headers: this.httpHeaders }); 21 | } 22 | 23 | postCommand(createCommand: CreateCommand) { 24 | return this.httpClient.post(this.url, createCommand, { headers: this.httpHeaders }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /frontend/src/app/services/reading.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpHeaders } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { Reading } from '../interfaces/reading'; 5 | import { CreateReading } from '../interfaces/create-reading'; 6 | import { environment } from '../../environments/environment'; 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class ReadingService { 12 | url: string = `http://${environment.host}:${environment.port}/api/v1/readings/`; 13 | httpHeaders: HttpHeaders = new HttpHeaders({ 14 | 'Content-Type': 'application/json' 15 | }); 16 | 17 | constructor(private httpClient: HttpClient) { } 18 | 19 | getReadings(): Observable { 20 | return this.httpClient.get(this.url, { headers: this.httpHeaders }); 21 | } 22 | 23 | postReading(createReading: CreateReading) { 24 | return this.httpClient.post(this.url, createReading, { headers: this.httpHeaders }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */ 2 | /* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */ 3 | { 4 | "compileOnSave": false, 5 | "compilerOptions": { 6 | "outDir": "./dist/out-tsc", 7 | "strict": true, 8 | "noImplicitOverride": true, 9 | "noPropertyAccessFromIndexSignature": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "skipLibCheck": true, 13 | "isolatedModules": true, 14 | "esModuleInterop": true, 15 | "experimentalDecorators": true, 16 | "moduleResolution": "bundler", 17 | "importHelpers": true, 18 | "target": "ES2022", 19 | "module": "ES2022" 20 | }, 21 | "angularCompilerOptions": { 22 | "enableI18nLegacyMessageIdFormat": false, 23 | "strictInjectionParameters": true, 24 | "strictInputAccessModifiers": true, 25 | "strictTemplates": true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/app/components/readings/readings.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Reading } from '../../interfaces/reading'; 3 | import { ReadingService } from '../../services/reading.service'; 4 | 5 | @Component({ 6 | selector: 'app-readings', 7 | imports: [], 8 | templateUrl: './readings.component.html', 9 | styleUrl: './readings.component.css' 10 | }) 11 | export class ReadingsComponent { 12 | readings: Reading[] = []; 13 | isLoading: boolean = false; 14 | 15 | constructor(private readingService: ReadingService) { } 16 | 17 | loadReadings(): void { 18 | this.isLoading = true; 19 | this.readingService.getReadings().subscribe({ 20 | next: (readings: Reading[]) => { 21 | this.readings = readings; 22 | console.log('Readings loaded successfully:', readings); 23 | this.isLoading = false; 24 | }, 25 | error: (error) => { 26 | console.error('Error loading readings:', error); 27 | this.isLoading = false; 28 | } 29 | }); 30 | return; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(async () => { 6 | await TestBed.configureTestingModule({ 7 | imports: [AppComponent], 8 | }).compileComponents(); 9 | }); 10 | 11 | it('should create the app', () => { 12 | const fixture = TestBed.createComponent(AppComponent); 13 | const app = fixture.componentInstance; 14 | expect(app).toBeTruthy(); 15 | }); 16 | 17 | it(`should have the 'frontend' title`, () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.componentInstance; 20 | expect(app.title).toEqual('frontend'); 21 | }); 22 | 23 | it('should render title', () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | fixture.detectChanges(); 26 | const compiled = fixture.nativeElement as HTMLElement; 27 | expect(compiled.querySelector('h1')?.textContent).toContain('Hello, frontend'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /frontend/src/app/components/navbar/navbar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { NgClass } from '@angular/common'; 3 | import { ActivatedRoute, NavigationEnd, Router, RouterModule } from '@angular/router'; 4 | import { setUser } from '../../signal'; 5 | 6 | @Component({ 7 | selector: 'app-navbar', 8 | imports: [ 9 | NgClass, 10 | RouterModule 11 | ], 12 | templateUrl: './navbar.component.html', 13 | styleUrl: './navbar.component.css' 14 | }) 15 | export class NavbarComponent implements OnInit { 16 | currentRoute: string = ""; 17 | 18 | constructor(private router: Router, private activatedRoute: ActivatedRoute) {} 19 | 20 | ngOnInit() { 21 | this.router.events.subscribe((event) => { 22 | if (event instanceof NavigationEnd) { 23 | this.currentRoute = this.activatedRoute.snapshot.url[0]?.path; 24 | } 25 | }); 26 | } 27 | 28 | isActiveRoute(route: string): boolean { 29 | return this.currentRoute === route; 30 | } 31 | 32 | signOut(): void { 33 | setUser(null); 34 | this.router.navigate(["/sign-in"]); 35 | return; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /frontend/src/app/components/commands/commands.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | Loading... 5 |
6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
IDUser EmailTelescope NameTarget Azimuth AngleTarget Elevation AngleCreated At
{{i + 1}} {{ getUserById(command.user_id)?.email_address ?? 'Unknown User' }} {{ command.telescope_id }} {{ command.target_az_angle }} ° {{ command.target_el_angle }} ° {{ command.created_at | date: 'short' }}
31 |
-------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test" 10 | }, 11 | "private": true, 12 | "dependencies": { 13 | "@angular/common": "^19.2.0", 14 | "@angular/compiler": "^19.2.0", 15 | "@angular/core": "^19.2.0", 16 | "@angular/forms": "^19.2.0", 17 | "@angular/platform-browser": "^19.2.0", 18 | "@angular/platform-browser-dynamic": "^19.2.0", 19 | "@angular/router": "^19.2.0", 20 | "@canvasjs/angular-charts": "^1.2.0", 21 | "@fortawesome/angular-fontawesome": "^1.0.0", 22 | "@fortawesome/fontawesome-free": "^6.7.2", 23 | "bootstrap": "^5.3.7", 24 | "rxjs": "~7.8.0", 25 | "tslib": "^2.3.0", 26 | "zone.js": "~0.15.0" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "^19.2.13", 30 | "@angular/cli": "^19.2.13", 31 | "@angular/compiler-cli": "^19.2.0", 32 | "@types/jasmine": "~5.1.0", 33 | "jasmine-core": "~5.6.0", 34 | "karma": "~6.4.0", 35 | "karma-chrome-launcher": "~3.2.0", 36 | "karma-coverage": "~2.2.0", 37 | "karma-jasmine": "~5.1.0", 38 | "karma-jasmine-html-reporter": "~2.1.0", 39 | "typescript": "~5.7.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /frontend/src/app/services/telescope.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { environment } from '../../environments/environment'; 3 | import { HttpClient, HttpHeaders } from '@angular/common/http'; 4 | import { Observable } from 'rxjs'; 5 | import { Telescope } from '../interfaces/telescope'; 6 | import { CreateTelescope } from '../interfaces/create-telescope'; 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class TelescopeService { 12 | url: string = `http://${environment.host}:${environment.port}/api/v1/telescopes/`; 13 | httpHeaders: HttpHeaders = new HttpHeaders({ 14 | 'Content-Type': 'application/json' 15 | }); 16 | 17 | constructor(private httpClient: HttpClient) { } 18 | 19 | getTelescopes(): Observable { 20 | return this.httpClient.get(this.url, { headers: this.httpHeaders}); 21 | } 22 | 23 | postTelescope(telescope: CreateTelescope): Observable { 24 | return this.httpClient.post(this.url, telescope, { headers: this.httpHeaders }); 25 | } 26 | 27 | putTelescope(telescope: Telescope): Observable { 28 | return this.httpClient.put(`${this.url}${telescope.id}`, telescope, { headers: this.httpHeaders }); 29 | } 30 | 31 | deleteTelescope(telescopeId: number): Observable { 32 | return this.httpClient.delete(`${this.url}${telescopeId}`, { headers: this.httpHeaders }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /backend/classes/Ifastapi.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, HTTPException 2 | from pydantic import BaseModel 3 | from typing import List 4 | import uvicorn 5 | app = FastAPI() 6 | 7 | class Task(BaseModel): 8 | title: str 9 | description: str = "" 10 | 11 | tasks = [] 12 | @app.post("/tasks/") 13 | def create_task(task: Task): 14 | tasks.append(task) 15 | return {"msg": "task added succesfully"} 16 | 17 | @app.get("/tasks/", response_model=List[Task]) 18 | def get_tasks(): 19 | return tasks 20 | 21 | @app.get("/tasks/{task_id}", response_model=Task) 22 | def get_task(task_id: int): 23 | if 0 <= task_id < len(tasks): 24 | return tasks[task_id] 25 | else: 26 | raise HTTPException(status_code=404, detail="Task not found") 27 | 28 | @app.put("/tasks/{task_id}", response_model=Task) 29 | def update_task(task_id: int, updated_task: Task): 30 | if 0 <= task_id < len(tasks): 31 | tasks[task_id] = updated_task 32 | return updated_task 33 | else: 34 | raise HTTPException(status_code=404, detail="Task not found") 35 | @app.delete("/tasks/{task_id}", response_model=Task) 36 | def delete_task(task_id: int): 37 | if 0 <= task_id < len(tasks): 38 | deleted_task = tasks.pop(task_id) 39 | return deleted_task 40 | else: 41 | raise HTTPException(status_code=404, detail="Task not found") 42 | 43 | if __name__ == "__main__": 44 | uvicorn.run(app, host="127.0.0.1", port=8000) -------------------------------------------------------------------------------- /frontend/src/app/services/user.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpHeaders } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs'; 4 | import { User } from '../interfaces/user'; 5 | import { environment } from '../../environments/environment'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class UserService { 11 | 12 | url: string = `http://${environment.host}:${environment.port}/api/v1/users` 13 | 14 | constructor(private httpClient: HttpClient) { } 15 | 16 | httpHeaders: HttpHeaders = new HttpHeaders({ 17 | 'Content-Type': 'application/json' 18 | }); 19 | 20 | getUser(userId: number): Observable { 21 | return this.httpClient.get(`${this.url}/${userId}`, { headers: this.httpHeaders }); 22 | } 23 | 24 | getUsers(): Observable { 25 | return this.httpClient.get(`${this.url}/`, { headers: this.httpHeaders }); 26 | } 27 | 28 | signIn(email_address: string, password: string): Observable { 29 | const user_data = { 30 | email_address: email_address, 31 | password: password 32 | } 33 | return this.httpClient.post(`${this.url}/auth`, user_data, { headers: this.httpHeaders }); 34 | } 35 | 36 | signUp(email_address: string, password: string): Observable { 37 | const user_data = { 38 | email_address: email_address, 39 | password: password 40 | } 41 | return this.httpClient.post(this.url, user_data, { headers: this.httpHeaders }); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /frontend/src/app/components/sign-in/sign-in.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; 3 | import { Router, RouterLink } from '@angular/router'; 4 | import { UserService } from '../../services/user.service'; 5 | import { User } from '../../interfaces/user'; 6 | import { CommonModule } from '@angular/common'; 7 | import { setUser } from '../../signal'; 8 | 9 | @Component({ 10 | selector: 'app-sign-in', 11 | imports: [ 12 | CommonModule, 13 | ReactiveFormsModule, 14 | RouterLink, 15 | ], 16 | templateUrl: './sign-in.component.html', 17 | styleUrl: './sign-in.component.css' 18 | }) 19 | export class SignInComponent { 20 | signInForm: FormGroup; 21 | 22 | constructor( 23 | private formBuilder: FormBuilder, 24 | private userService: UserService, 25 | private router: Router 26 | ) { 27 | this.signInForm = this.formBuilder.group({ 28 | email: ['', [Validators.required, Validators.email]], 29 | password: ['', Validators.required] 30 | }); 31 | } 32 | 33 | onSubmit() { 34 | if (this.signInForm.valid) { 35 | this.router.navigate(['/telescopes']); 36 | const { email, password } = this.signInForm.value; 37 | this.userService.signIn(email, password).subscribe({ 38 | next: (user: User) => { 39 | console.log('User signed in successfully:', user); 40 | setUser(user); 41 | this.router.navigate(['/telescopes']); 42 | }, 43 | error: (error) => { 44 | // Handle the error 45 | console.error('Sign-in error:', error); 46 | } 47 | }); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Frontend 2 | 3 | This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 19.2.13. 4 | 5 | ## Development server 6 | 7 | To start a local development server, run: 8 | 9 | ```bash 10 | ng serve 11 | ``` 12 | 13 | Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files. 14 | 15 | ## Code scaffolding 16 | 17 | Angular CLI includes powerful code scaffolding tools. To generate a new component, run: 18 | 19 | ```bash 20 | ng generate component component-name 21 | ``` 22 | 23 | For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run: 24 | 25 | ```bash 26 | ng generate --help 27 | ``` 28 | 29 | ## Building 30 | 31 | To build the project run: 32 | 33 | ```bash 34 | ng build 35 | ``` 36 | 37 | This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed. 38 | 39 | ## Running unit tests 40 | 41 | To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command: 42 | 43 | ```bash 44 | ng test 45 | ``` 46 | 47 | ## Running end-to-end tests 48 | 49 | For end-to-end (e2e) testing, run: 50 | 51 | ```bash 52 | ng e2e 53 | ``` 54 | 55 | Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs. 56 | 57 | ## Additional Resources 58 | 59 | For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page. 60 | -------------------------------------------------------------------------------- /frontend/src/app/components/navbar/navbar.component.html: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /frontend/src/app/components/sign-up/sign-up.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { AbstractControl, FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; 3 | import { Router, RouterLink } from '@angular/router'; 4 | import { UserService } from '../../services/user.service'; 5 | import { User } from '../../interfaces/user'; 6 | import { CommonModule } from '@angular/common'; 7 | 8 | @Component({ 9 | selector: 'app-sign-up', 10 | imports: [ 11 | CommonModule, 12 | ReactiveFormsModule, 13 | RouterLink, 14 | ], 15 | templateUrl: './sign-up.component.html', 16 | styleUrl: './sign-up.component.css' 17 | }) 18 | export class SignUpComponent { 19 | signUpForm: FormGroup; 20 | 21 | constructor( 22 | private formBuilder: FormBuilder, 23 | private userService: UserService, 24 | private router: Router 25 | ) { 26 | this.signUpForm = this.formBuilder.group({ 27 | email: ['', [Validators.required, Validators.email]], 28 | password: ['', [Validators.required, Validators.minLength(5)]], 29 | confirmPassword: ['', [Validators.required, this.confirmPasswordValidator.bind(this)]] 30 | }); 31 | } 32 | 33 | confirmPasswordValidator(control: AbstractControl) { 34 | const password = this.signUpForm?.get('password')?.value; 35 | const confirmPassword = control.value; 36 | return password === confirmPassword ? null : { passwordMismatch: 'Passwords do not match' }; 37 | } 38 | 39 | onSubmit() { 40 | if (this.signUpForm.valid) { 41 | const { email, password } = this.signUpForm.value; 42 | this.userService.signUp(email, password).subscribe({ 43 | next: (user: User) => { 44 | console.log('User signed up successfully:', user); 45 | this.router.navigate(['/sign-in']); 46 | }, 47 | error: (error) => { 48 | // Handle the error 49 | console.error('Sign-up error:', error); 50 | } 51 | }); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /frontend/src/app/components/commands/commands.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { CommandService } from '../../services/command.service'; 3 | import { Command } from '../../interfaces/command'; 4 | import { CommonModule } from '@angular/common'; 5 | import { UserService } from '../../services/user.service'; 6 | import { User } from '../../interfaces/user'; 7 | 8 | @Component({ 9 | selector: 'app-commands', 10 | imports: [CommonModule], 11 | templateUrl: './commands.component.html', 12 | styleUrl: './commands.component.css' 13 | }) 14 | export class CommandsComponent implements OnInit { 15 | isLoading: boolean = false; 16 | areCommandsLoading: boolean = false; 17 | areUsersLoading: boolean = false; 18 | 19 | commands: Command[] = []; 20 | users: User[] = []; 21 | userEmails: { [key: number]: string } = {}; 22 | 23 | constructor(private commandService: CommandService, private userService: UserService) {} 24 | 25 | ngOnInit(): void { 26 | this.loadUsers(); 27 | this.loadCommands(); 28 | this.isLoading = !this.areCommandsLoading && !this.areUsersLoading; 29 | this.commands.forEach((command) => { 30 | this.userEmails[command.user_id] = this.users.find(u => u.id == command.user_id)?.email_address ?? 'Unknown User'; 31 | }); 32 | } 33 | 34 | loadCommands(): void { 35 | this.areCommandsLoading = true; 36 | this.commandService.getCommands().subscribe({ 37 | next: (commands: Command[]) => { 38 | this.commands = commands; 39 | console.log('Commands loaded successfully:', commands); 40 | this.areCommandsLoading = false; 41 | }, 42 | error: (error) => { 43 | console.error('Error loading commands:', error); 44 | this.areCommandsLoading = false; 45 | } 46 | }); 47 | } 48 | 49 | loadUsers(): void { 50 | this.areUsersLoading = true; 51 | this.userService.getUsers().subscribe({ 52 | next: (users: User[]) => { 53 | console.log('Users loaded successfully:', users); 54 | this.areUsersLoading = false; 55 | }, 56 | error: (error) => { 57 | console.error('Error loading users:', error); 58 | this.areUsersLoading = false; 59 | } 60 | }); 61 | } 62 | 63 | getUserById(userId: number): User | undefined { 64 | return this.users.find(user => user.id === userId); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /backend/endpoints/sources.py: -------------------------------------------------------------------------------- 1 | from fastapi import HTTPException 2 | from classes.source import CreateSource, Source 3 | from classes import Database 4 | 5 | table_name: str = 'source' 6 | 7 | def get_source(source_id: int) -> Source: 8 | db = Database() 9 | _, db_select_source_outputs = db.read(table_name, criteria={'id': source_id}) 10 | if db_select_source_outputs: 11 | id, name = db_select_source_outputs[0] 12 | return Source(id=int(id), name=str(name)) 13 | else: 14 | raise HTTPException(status_code=404, detail=f"Source with ID {source_id} not found.") 15 | 16 | def get_sources() -> list[Source]: 17 | db = Database() 18 | _, db_select_source_outputs = db.read(table_name) 19 | sources = [] 20 | for db_select_source_output in db_select_source_outputs: 21 | id, name = db_select_source_output 22 | sources.append(Source(id=int(id), name=str(name))) 23 | return sources 24 | 25 | def post_source(source: CreateSource) -> Source: 26 | db = Database() 27 | _, _ = db.insert(table_name, source.__dict__) 28 | _, db_select_source_outputs = db.read(table_name, criteria=source.__dict__) 29 | id, name = db_select_source_outputs[-1] 30 | return Source(id=int(id), name=str(name)) 31 | 32 | def delete_source(source_id: int) -> Source: 33 | db = Database() 34 | _, db_select_source_outputs = db.read(table_name, criteria={'id': source_id}) 35 | if db_select_source_outputs: 36 | id, name = db_select_source_outputs[0] 37 | source = Source(id=int(id), name=str(name)) 38 | success, _ = db.delete(table_name, criteria={'id': source_id}) 39 | if success: 40 | return source 41 | else: 42 | raise HTTPException(status_code=500, detail="Failed to delete source.") 43 | else: 44 | raise HTTPException(status_code=404, detail=f"Source with ID {source_id} not found.") 45 | 46 | def update_source(source_id: int, source: Source) -> Source: 47 | db = Database() 48 | success, _ = db.update(table_name, criteria={'id': source_id}, data=source.__dict__) 49 | if success: 50 | _, db_select_source_outputs = db.read(table_name, criteria={'id': source_id}) 51 | id, name = db_select_source_outputs[0] 52 | return Source(id=int(id), name=str(name)) 53 | else: 54 | raise HTTPException(status_code=500, detail="Failed to update source.") 55 | -------------------------------------------------------------------------------- /frontend/src/app/components/sign-in/sign-in.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |

9 | Logo 10 | Sign In 11 |

12 |
13 |
14 | 15 | 16 |
17 |
Email is required.
18 |
Email is not valid.
19 |
20 |
21 |
22 | 23 | 24 |
25 |
Password is required.
26 |
27 |
28 | 29 |
30 | Don't have an account? Sign Up 31 |
32 |
33 |
34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /frontend/src/app/components/telescopes/telescopes.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 54 | 55 | 56 |
#Telescope NameHealth StatusCreated AtAction
17 | 18 | 22 | 26 |
31 | No Data! 32 |
{{ i + 1 }} {{ telescope.name }} {{ telescope.health_status }} {{ telescope.created_at | date }} 41 | 45 | 49 | 53 |
57 |
58 |
59 | 60 | 61 |
62 |
63 | Loading... 64 |
65 |
66 |
67 | 68 | 69 | -------------------------------------------------------------------------------- /frontend/src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | import { CommandsComponent } from './components/commands/commands.component'; 5 | import { ReadingsComponent } from './components/readings/readings.component'; 6 | import { CamComponent } from './components/cam/cam.component'; 7 | import { SignInComponent } from './components/sign-in/sign-in.component'; 8 | import { TelescopesComponent } from './components/telescopes/telescopes.component'; 9 | import { TelescopeDetailsComponent } from './components/telescope-details/telescope-details.component'; 10 | import { UsersComponent } from './components/users/users.component'; 11 | import { SourcesComponent } from './components/sources/sources.component'; 12 | import { SourceDetailsComponent } from './components/source-details/source-details.component'; 13 | import { UserDetailsComponent } from './components/user-details/user-details.component'; 14 | import { AppLayoutComponent } from './components/app-layout/app-layout.component'; 15 | import { SignUpComponent } from './components/sign-up/sign-up.component'; 16 | import { userGuard } from './guards/user.guard'; 17 | 18 | export const routes: Routes = [ 19 | { 20 | path: '', redirectTo: 'sign-in', pathMatch: 'full' 21 | }, 22 | { 23 | path: 'sign-in', component: SignInComponent 24 | }, 25 | { 26 | path: 'sign-up', component: SignUpComponent 27 | }, 28 | { 29 | path: '', component: AppLayoutComponent, canActivateChild: [ userGuard ], children: [ 30 | { 31 | path: 'telescopes', component: TelescopesComponent 32 | }, 33 | { 34 | path: 'telescopes/:id/details', component: TelescopeDetailsComponent 35 | }, 36 | { 37 | path: 'telescopes/:id/cam', component: CamComponent 38 | }, 39 | { 40 | path: 'telescopes/:id/commands', component: CommandsComponent 41 | }, 42 | { 43 | path: 'telescopes/:id/readings', component: ReadingsComponent 44 | }, 45 | { 46 | path: 'commands', component: CommandsComponent 47 | }, 48 | { 49 | path: 'readings', component: ReadingsComponent 50 | }, 51 | { 52 | path: 'users', component: UsersComponent, children: [ 53 | { 54 | path: ':id/details', component: UserDetailsComponent 55 | } 56 | ] 57 | }, 58 | { 59 | path: 'sources', component: SourcesComponent, children: [ 60 | { 61 | path: ':id/details', component: SourceDetailsComponent 62 | } 63 | ] 64 | } 65 | ] 66 | },/* 67 | { 68 | path: '**', redirectTo: 'sign-in', pathMatch: 'full' 69 | }*/ 70 | ]; 71 | 72 | @NgModule({ 73 | imports: [RouterModule.forRoot(routes)], 74 | exports: [RouterModule] 75 | }) 76 | export class AppRoutingModule { } 77 | -------------------------------------------------------------------------------- /backend/endpoints/telescopes.py: -------------------------------------------------------------------------------- 1 | from fastapi import HTTPException 2 | from classes.telescope import CreateTelescope, Telescope 3 | from datetime import datetime 4 | from classes import Database 5 | 6 | table_name: str = 'telescope' 7 | 8 | 9 | def get_telescopes() -> list[Telescope]: 10 | # Read telescopes from db 11 | db = Database() 12 | _, db_select_telescope_outputs = db.read(table_name) 13 | telescopes = [] 14 | for db_select_telescope_output in db_select_telescope_outputs: 15 | id, name, health_status, created_at = db_select_telescope_output 16 | telescopes.append(Telescope(id=int(id), name=str(name), health_status=str(health_status), created_at=datetime.fromisoformat(str(created_at)))) 17 | 18 | return telescopes 19 | 20 | def post_telescope(telescope: CreateTelescope) -> Telescope: 21 | # Insert new telescope data 22 | db = Database() 23 | _, _ = db.insert(table_name, telescope.__dict__) 24 | 25 | # Read newly inserted telescope from db 26 | _, db_select_telescope_outputs = db.read(table_name, criteria=telescope.__dict__) 27 | id, name, health_status, created_at = db_select_telescope_outputs[-1] 28 | 29 | print(f"Created telescope: {created_at}") 30 | return Telescope(id=int(id), name=str(name), health_status=str(health_status), created_at=datetime.fromisoformat(str(created_at))) 31 | 32 | def get_telescope(telescope_id: int) -> Telescope: 33 | # Read telescope from db by id 34 | db = Database() 35 | _, db_select_telescope_outputs = db.read(table_name, criteria={'id': telescope_id}) 36 | if db_select_telescope_outputs: 37 | id, name, health_status, created_at = db_select_telescope_outputs[0] 38 | return Telescope(id=int(id), name=str(name), health_status=str(health_status), created_at=datetime.fromisoformat(str(created_at))) 39 | else: 40 | return None 41 | 42 | def delete_telescope(telescope_id: int) -> Telescope: 43 | # Delete telescope from db by id 44 | db = Database() 45 | _, db_select_telescope_outputs = db.read(table_name, criteria={'id': telescope_id}) 46 | if db_select_telescope_outputs: 47 | id, name, health_status, created_at= db_select_telescope_outputs[0] 48 | telescope = Telescope(id=int(id), name=str(name), health_status=str(health_status), created_at=datetime.fromisoformat(str(created_at))) 49 | success, _ = db.delete(table_name, criteria={'id': telescope_id}) 50 | if success: 51 | return telescope 52 | else: 53 | raise HTTPException(status_code=500, detail="Failed to delete telescope.") 54 | else: 55 | raise HTTPException(status_code=404, detail=f"Telescope with ID {telescope_id} not found.") 56 | 57 | def update_telescope(telescope_id: int, telescope: Telescope) -> Telescope: 58 | # Update telescope in db by id 59 | db = Database() 60 | success, _ = db.update(table_name, criteria={'id': telescope_id}, data=telescope.__dict__) 61 | if success: 62 | _, db_select_telescope_outputs = db.read(table_name, criteria={'id': telescope_id}) 63 | id, name, health_status, created_at = db_select_telescope_outputs[0] 64 | return Telescope(id=int(id), name=str(name), health_status=str(health_status), created_at=datetime.fromisoformat(str(created_at))) 65 | else: 66 | return None -------------------------------------------------------------------------------- /backend/endpoints/positions.py: -------------------------------------------------------------------------------- 1 | from fastapi import HTTPException 2 | from classes.position import CreatePosition, Position 3 | from classes import Database 4 | from datetime import datetime 5 | 6 | table_name: str = 'position' 7 | 8 | def get_position(position_id: int) -> Position: 9 | db = Database() 10 | _, db_select_position_outputs = db.read(table_name, criteria={'id': position_id}) 11 | if db_select_position_outputs: 12 | id, datetime_val, created_at = db_select_position_outputs[0] 13 | return Position( 14 | id=int(id), 15 | datetime=datetime.fromisoformat(str(datetime_val)), 16 | created_at=datetime.fromisoformat(str(created_at)) 17 | ) 18 | else: 19 | raise HTTPException(status_code=404, detail=f"Position with ID {position_id} not found.") 20 | 21 | def get_positions() -> list[Position]: 22 | db = Database() 23 | _, db_select_position_outputs = db.read(table_name) 24 | positions = [] 25 | for db_select_position_output in db_select_position_outputs: 26 | id, datetime_val, created_at = db_select_position_output 27 | positions.append(Position( 28 | id=int(id), 29 | datetime=datetime.fromisoformat(str(datetime_val)), 30 | created_at=datetime.fromisoformat(str(created_at)) 31 | )) 32 | return positions 33 | 34 | def post_position(position: CreatePosition) -> Position: 35 | db = Database() 36 | _, _ = db.insert(table_name, position.__dict__) 37 | _, db_select_position_outputs = db.read(table_name, criteria=position.__dict__) 38 | id, datetime_val, created_at = db_select_position_outputs[-1] 39 | return Position( 40 | id=int(id), 41 | datetime=datetime.fromisoformat(str(datetime_val)), 42 | created_at=datetime.fromisoformat(str(created_at)) 43 | ) 44 | 45 | def delete_position(position_id: int) -> Position: 46 | db = Database() 47 | _, db_select_position_outputs = db.read(table_name, criteria={'id': position_id}) 48 | if db_select_position_outputs: 49 | id, datetime_val, created_at = db_select_position_outputs[0] 50 | position = Position( 51 | id=int(id), 52 | datetime=datetime.fromisoformat(str(datetime_val)), 53 | created_at=datetime.fromisoformat(str(created_at)) 54 | ) 55 | success, _ = db.delete(table_name, criteria={'id': position_id}) 56 | if success: 57 | return position 58 | else: 59 | raise HTTPException(status_code=500, detail="Failed to delete position.") 60 | else: 61 | raise HTTPException(status_code=404, detail=f"Position with ID {position_id} not found.") 62 | 63 | def update_position(position_id: int, position: Position) -> Position: 64 | db = Database() 65 | success, _ = db.update(table_name, criteria={'id': position_id}, data=position.__dict__) 66 | if success: 67 | _, db_select_position_outputs = db.read(table_name, criteria={'id': position_id}) 68 | id, datetime_val, created_at = db_select_position_outputs[0] 69 | return Position( 70 | id=int(id), 71 | datetime=datetime.fromisoformat(str(datetime_val)), 72 | created_at=datetime.fromisoformat(str(created_at)) 73 | ) 74 | else: 75 | raise HTTPException(status_code=500, detail="Failed to update position.") 76 | -------------------------------------------------------------------------------- /frontend/src/app/components/sign-up/sign-up.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |

9 | Logo 10 | Sign Up 11 |

12 |
13 |
14 | 15 | 16 |
17 |
Email is required.
18 |
Email is not valid.
19 |
20 |
21 |
22 | 23 | 24 |
25 |
Password is required.
26 |
Password must be at least 5 characters long.
27 |
28 |
29 |
30 | 31 | 32 |
33 |
Confirm Password is required.
34 |
{{ signUpForm.get('confirmPassword')?.errors?.['passwordMismatch'] }}
35 |
36 |
37 |
38 |
39 | 40 |
41 | Already have an account? Sign In 42 |
43 |
44 |
45 |
46 |
47 |
48 | -------------------------------------------------------------------------------- /backend/endpoints/users.py: -------------------------------------------------------------------------------- 1 | from fastapi import HTTPException 2 | from classes.user import CreateUser, User 3 | from datetime import datetime 4 | from classes import Database 5 | 6 | table_name: str = 'user' 7 | 8 | def get_users() -> list[User]: 9 | db = Database() 10 | _, db_select_user_outputs = db.read(table_name) 11 | users = [] 12 | for db_select_user_output in db_select_user_outputs: 13 | id, email_address, password, created_at = db_select_user_output 14 | users.append(User(id=int(id), email_address=str(email_address), password=str(password), created_at=datetime.fromisoformat(str(created_at)))) 15 | 16 | return users 17 | 18 | def post_user(user: CreateUser) -> User: 19 | # Insert new user data 20 | db = Database() 21 | _, _ = db.insert(table_name, user.__dict__) 22 | # Read newly inserted user from db 23 | _, db_select_user_outputs = db.read(table_name, criteria=user.__dict__) 24 | id, email_address, password, created_at = db_select_user_outputs[-1] 25 | return User(id=int(id), email_address=str(email_address), password=str(password), created_at=datetime.fromisoformat(str(created_at))) 26 | 27 | def get_user(user_id: int) -> User: 28 | # Read user from db by id 29 | db = Database() 30 | _, db_select_user_outputs = db.read(table_name, criteria={'id': user_id}) 31 | if db_select_user_outputs: 32 | id, email_address, password, created_at = db_select_user_outputs[0] 33 | return User(id=int(id), email_address=str(email_address), password=str(password), created_at=datetime.fromisoformat(str(created_at))) 34 | else: 35 | return None 36 | 37 | def auth_user(user: CreateUser) -> User: 38 | # Authenticate user by email and password 39 | db = Database() 40 | _, db_select_user_outputs = db.read(table_name, criteria={'email_address': user.email_address, 'password': user.password}) 41 | if db_select_user_outputs: 42 | id, email_address, password, created_at = db_select_user_outputs[0] 43 | return User(id=int(id), email_address=str(email_address), password=str(password), created_at=datetime.fromisoformat(str(created_at))) 44 | else: 45 | raise HTTPException(status_code=401, detail="Invalid email or password.") 46 | 47 | def delete_user(user_id: int) -> User: 48 | # Delete user from db by id 49 | db = Database() 50 | _, db_select_user_outputs = db.read(table_name, criteria={'id': user_id}) 51 | if db_select_user_outputs: 52 | id, email_address, password, created_at = db_select_user_outputs[0] 53 | user = User(id=int(id), email_address=str(email_address), password=str(password), created_at=datetime.fromisoformat(str(created_at))) 54 | success, _ = db.delete(table_name, criteria={'id': user_id}) 55 | if success: 56 | return user 57 | else: 58 | raise HTTPException(status_code=500, detail="Failed to delete user.") 59 | else: 60 | raise HTTPException(status_code=404, detail=f"User with ID {user_id} not found.") 61 | 62 | def update_user(user_id: int, user: User) -> User: 63 | # Update user in db by id 64 | db = Database() 65 | success, _ = db.update(table_name, criteria={'id': user_id}, data=user.__dict__) 66 | if success: 67 | _, db_select_user_outputs = db.read(table_name, criteria={'id': user_id}) 68 | id, email_address, password, created_at = db_select_user_outputs[0] 69 | return User(id=int(id), email_address=str(email_address), password=str(password), created_at=datetime.fromisoformat(str(created_at))) 70 | else: 71 | return None -------------------------------------------------------------------------------- /frontend/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "frontend": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "", 10 | "sourceRoot": "src", 11 | "prefix": "app", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:application", 15 | "options": { 16 | "outputPath": "dist/frontend", 17 | "index": "src/index.html", 18 | "browser": "src/main.ts", 19 | "polyfills": [ 20 | "zone.js" 21 | ], 22 | "tsConfig": "tsconfig.app.json", 23 | "assets": [ 24 | { 25 | "glob": "**/*", 26 | "input": "public" 27 | } 28 | ], 29 | "styles": [ 30 | "src/styles.css", 31 | "node_modules/bootstrap/dist/css/bootstrap.min.css", 32 | "node_modules/@fortawesome/fontawesome-free/css/all.min.css" 33 | ], 34 | "scripts": [ 35 | "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js" 36 | ] 37 | }, 38 | "configurations": { 39 | "production": { 40 | "budgets": [ 41 | { 42 | "type": "initial", 43 | "maximumWarning": "500kB", 44 | "maximumError": "1MB" 45 | }, 46 | { 47 | "type": "anyComponentStyle", 48 | "maximumWarning": "4kB", 49 | "maximumError": "8kB" 50 | } 51 | ], 52 | "outputHashing": "all" 53 | }, 54 | "development": { 55 | "optimization": false, 56 | "extractLicenses": false, 57 | "sourceMap": true, 58 | "fileReplacements": [ 59 | { 60 | "replace": "src/environments/environment.ts", 61 | "with": "src/environments/environment.development.ts" 62 | } 63 | ] 64 | } 65 | }, 66 | "defaultConfiguration": "production" 67 | }, 68 | "serve": { 69 | "builder": "@angular-devkit/build-angular:dev-server", 70 | "configurations": { 71 | "production": { 72 | "buildTarget": "frontend:build:production" 73 | }, 74 | "development": { 75 | "buildTarget": "frontend:build:development" 76 | } 77 | }, 78 | "defaultConfiguration": "development" 79 | }, 80 | "extract-i18n": { 81 | "builder": "@angular-devkit/build-angular:extract-i18n" 82 | }, 83 | "test": { 84 | "builder": "@angular-devkit/build-angular:karma", 85 | "options": { 86 | "polyfills": [ 87 | "zone.js", 88 | "zone.js/testing" 89 | ], 90 | "tsConfig": "tsconfig.spec.json", 91 | "assets": [ 92 | { 93 | "glob": "**/*", 94 | "input": "public" 95 | } 96 | ], 97 | "styles": [ 98 | "src/styles.css" 99 | ], 100 | "scripts": [] 101 | } 102 | } 103 | } 104 | } 105 | }, 106 | "cli": { 107 | "analytics": false 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /backend/endpoints/readings.py: -------------------------------------------------------------------------------- 1 | from fastapi import HTTPException 2 | from classes.reading import CreateReading, Reading 3 | from datetime import datetime 4 | from classes import Database 5 | 6 | table_name: str = 'reading' 7 | 8 | def get_reading(reading_id: int) -> Reading: 9 | db = Database() 10 | _,db_select_reading_outputs = db.read(table_name, criteria={'id': reading_id}) 11 | if db_select_reading_outputs: 12 | id, telescope_id, az_angle, el_angle, health_status, movement_status, created_at =db_select_reading_outputs[0] 13 | return Reading(id=int(id),telescope_id=int(telescope_id),az_angle=float(az_angle), el_angle=float(el_angle),health_status=str(health_status), 14 | movement_status=str(movement_status), created_at=datetime.fromisoformat(str(created_at))) 15 | else: 16 | return None 17 | 18 | def get_readings() -> list[Reading]: 19 | db = Database() 20 | _, db_select_reading_outputs = db.read(table_name) 21 | readings =[] 22 | for db_select_reading_output in db_select_reading_outputs: 23 | id, telescope_id, az_angle, el_angle, health_status, movement_status, created_at = db_select_reading_output 24 | readings.append(Reading(id=int(id), telescope_id=int(telescope_id), az_angle=float(az_angle), el_angle=float(el_angle),health_status=str(health_status), 25 | movement_status=str(movement_status), created_at=datetime.fromisoformat(str(created_at)))) 26 | return readings 27 | def get_latest_reading(telescope_id: int) -> Reading: 28 | db = Database() 29 | _, db_select_reading_outputs = db.read(table_name, criteria={'telescope_id': telescope_id}) 30 | if db_select_reading_outputs: 31 | id, telescope_id, az_angle, el_angle, health_status, movement_status, created_at = db_select_reading_outputs[-1] 32 | return Reading(id=int(id), telescope_id=int(telescope_id),az_angle=float(az_angle), el_angle=float(el_angle), health_status=str(health_status), 33 | movement_status=str(movement_status), created_at=datetime.fromisoformat(str(created_at))) 34 | else: 35 | raise HTTPException(status_code=404, detail=f"No readings found for telescope ID {telescope_id}.") 36 | def post_reading(reading: CreateReading) -> Reading: 37 | db = Database() 38 | _,_ = db.insert(table_name, reading.__dict__) 39 | _, db_select_reading_outputs = db.read(table_name, criteria=reading.__dict__) 40 | id, telescope_id, az_angle, el_angle, health_status, movement_status, created_at = db_select_reading_outputs[-1] 41 | return Reading(id=int(id), telescope_id=int(telescope_id),az_angle=float(az_angle), el_angle=float(el_angle), 42 | health_status= str(health_status),movement_status=str(movement_status), created_at=datetime.fromisoformat(str(created_at))) 43 | 44 | def delete_reading(reading_id: int) -> Reading: 45 | db = Database() 46 | _, db_select_reading_outputs = db.read(table_name, criteria={'id': reading_id}) 47 | if db_select_reading_outputs: 48 | id, telescope_id, az_angle, el_angle, health_status, movement_status, created_at = db_select_reading_outputs[0] 49 | reading = Reading(id=int(id), telescope_id=int(telescope_id),az_angle=float(az_angle), el_angle=float(el_angle), health_status=str(health_status), movement_status=str(movement_status), created_at=datetime.fromisoformat(str(created_at))) 50 | success, _ =db.delete(table_name, criteria={'id': reading_id}) 51 | if success: 52 | return reading 53 | else: 54 | return HTTPException(status_code=500, detail="Failed to delete reading.") 55 | else: 56 | raise HTTPException(status_code=404,detail=f"Reading with ID {reading_id} not found.") 57 | 58 | 59 | def update_reading(reading_id: int, reading: Reading) -> Reading: 60 | db = Database() 61 | success, _ =db.update(table_name, criteria={'id': reading_id}, data=reading.__dict__) 62 | if success: 63 | _, db_select_reading_outputs = db.read(table_name, criteria={'id': reading_id}) 64 | id, telescope_id, az_angle, el_angle, health_status, movement_status, created_at = db_select_reading_outputs[0] 65 | return Reading(id=int(id), telescope_id=int(telescope_id),az_angle=float(az_angle),el_angle=float(el_angle), health_status=str(health_status), 66 | movement_status=str(movement_status), created_at=datetime.fromisoformat(str(created_at))) 67 | else: 68 | return None 69 | 70 | -------------------------------------------------------------------------------- /frontend/src/app/components/cam/cam.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |

Sensor Readings Panel

8 |
9 |
10 |
11 | Health 12 |
13 | Status: {{ ['Normal', 'Warning', 'Error'][0] }}
14 |
15 | GPS 16 |
17 | Latitude: {{ 10 }}
18 | Longitude: {{ 10 }}
19 | Altitude: {{ 10 }} 20 |
21 |
22 |
23 | Magnetometer 24 |
25 | Magnetic Field X: {{ 10 }}
26 | Magnetic Field Y: {{ 10 }}
27 | Magnetic Field Z: {{ 10 }} 28 |
29 | Rotary Encoder 30 |
31 | Azimuth Angle: {{ 10 }}
32 | Elevation Angle: {{ 10 }} 33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |

Telescope Pointing

46 |
47 |
48 | 49 | 50 |
51 |
52 |
53 |
54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 |
63 | 64 |
65 |
66 | 67 | 68 |
69 | 70 |
71 |
72 | 73 | 76 | 77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |

Monitor Panel

88 |
89 |
90 |
91 | 92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | -------------------------------------------------------------------------------- /frontend/src/app/components/telescopes/telescopes.component.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { Component, OnInit } from '@angular/core'; 3 | import { Router, RouterOutlet } from '@angular/router'; 4 | import { Telescope } from '../../interfaces/telescope'; 5 | import { FormsModule } from '@angular/forms'; 6 | import { TelescopeService } from '../../services/telescope.service'; 7 | import { CreateTelescope } from '../../interfaces/create-telescope'; 8 | 9 | @Component({ 10 | selector: 'app-telescopes', 11 | imports: [ 12 | RouterOutlet, 13 | CommonModule, 14 | FormsModule, 15 | ], 16 | templateUrl: './telescopes.component.html', 17 | styleUrl: './telescopes.component.css' 18 | }) 19 | export class TelescopesComponent implements OnInit{ 20 | isLoading: boolean = true; 21 | telescopes: Telescope[] = []; 22 | 23 | newTelescopeName: string = ""; 24 | 25 | constructor(private router: Router, private telescopeService: TelescopeService) {} 26 | 27 | ngOnInit(): void { 28 | this.isLoading = true; 29 | this.loadTelescopes(); 30 | return; 31 | } 32 | 33 | loadTelescopes(): void { 34 | this.telescopeService.getTelescopes().subscribe({ 35 | next: (telescopes: Telescope[]) => { 36 | this.telescopes = telescopes; 37 | console.log(`Telescopes: ${telescopes}`); 38 | this.isLoading = false; 39 | }, 40 | error: (err: Error) => { 41 | console.error(err); 42 | this.isLoading = false; 43 | } 44 | }); 45 | return; 46 | } 47 | 48 | addTelescope(): void { 49 | this.isLoading = true; 50 | if (this.newTelescopeName.trim() === "") { 51 | console.error("Telescope name cannot be empty"); 52 | this.isLoading = false; 53 | return; 54 | } 55 | if (this.telescopes.some(telescope => telescope.name === this.newTelescopeName)) { 56 | console.error(`Telescope with name ${this.newTelescopeName} already exists`); 57 | this.isLoading = false; 58 | return; 59 | } 60 | const newTelescope: CreateTelescope = { 61 | name: this.newTelescopeName, 62 | health_status: 1, // Meaning the telescope is healthy 63 | }; 64 | this.telescopeService.postTelescope(newTelescope).subscribe({ 65 | next: (telescope: Telescope) => { 66 | console.log(`Telescope added: ${telescope}`); 67 | this.telescopes.push(telescope); 68 | this.isLoading = false; 69 | }, 70 | error: (err: Error) => { 71 | console.error(`Error adding telescope: ${err}`); 72 | this.isLoading = false; 73 | } 74 | }); 75 | return; 76 | } 77 | 78 | controlTelescope(telescopeId: number): void { 79 | this.router.navigate(["/telescopes", telescopeId, "cam"]); 80 | return; 81 | } 82 | 83 | editTelescope(name: string): void { 84 | let telescopeToEdit = this.telescopes.find(telescope => telescope.name === name); 85 | if (telescopeToEdit) { 86 | const newName = prompt("Enter new name for the telescope:", telescopeToEdit.name); 87 | if (!newName || newName.trim() === "") { 88 | console.error("Telescope name cannot be empty"); 89 | return; 90 | } 91 | if (this.telescopes.some(telescope => telescope.name === newName && telescope.id !== telescopeToEdit.id)) { 92 | console.error(`Telescope with name ${newName} already exists`); 93 | return; 94 | } 95 | telescopeToEdit.name = newName; 96 | this.isLoading = true; 97 | this.telescopeService.putTelescope(telescopeToEdit).subscribe({ 98 | next: (updatedTelescope: Telescope) => { 99 | console.log(`Telescope updated: ${updatedTelescope}`); 100 | const index = this.telescopes.findIndex(telescope => telescope.id === updatedTelescope.id); 101 | if (index !== -1) { 102 | this.telescopes[index] = updatedTelescope; 103 | } 104 | }, 105 | error: (err: Error) => { 106 | console.error(`Error updating telescope: ${err}`); 107 | } 108 | }); 109 | } 110 | this.isLoading = false; 111 | return; 112 | } 113 | 114 | deleteTelescope(telescopeName: string): void { 115 | const telescopeToDelete = this.telescopes.find(telescope => telescope.name === telescopeName); 116 | if (telescopeToDelete) { 117 | this.isLoading = true; 118 | this.telescopeService.deleteTelescope(telescopeToDelete.id).subscribe({ 119 | next: () => { 120 | console.log(`Telescope deleted: ${telescopeName}`); 121 | this.telescopes = this.telescopes.filter(telescope => telescope.id !== telescopeToDelete.id); 122 | this.isLoading = false; 123 | }, 124 | error: (err: Error) => { 125 | console.error(`Error deleting telescope: ${err}`); 126 | this.isLoading = false; 127 | } 128 | }); 129 | } 130 | return; 131 | } 132 | } 133 | 134 | -------------------------------------------------------------------------------- /backend/endpoints/commands.py: -------------------------------------------------------------------------------- 1 | from fastapi import HTTPException 2 | from classes.command import CreateCommand, Command 3 | from classes import Database 4 | from datetime import datetime 5 | 6 | table_name: str = 'command' 7 | 8 | def get_command(command_id: int) -> Command: 9 | db = Database() 10 | _, db_select_command_outputs = db.read(table_name, criteria={'id': command_id}) 11 | if db_select_command_outputs: 12 | id, user_id, telescope_id, target_az_angle, target_el_angle, created_at = db_select_command_outputs[0] 13 | return Command( 14 | id=int(id), 15 | user_id=str(user_id), 16 | telescope_id=str(telescope_id), 17 | target_az_angle=float(target_az_angle), 18 | target_el_angle=float(target_el_angle), 19 | created_at=datetime.fromisoformat(str(created_at)) 20 | ) 21 | else: 22 | raise HTTPException(status_code=404, detail=f"Command with ID {command_id} not found.") 23 | 24 | def get_commands() -> list[Command]: 25 | db = Database() 26 | _, db_select_command_outputs = db.read(table_name) 27 | commands = [] 28 | for db_select_command_output in db_select_command_outputs: 29 | id, user_id, telescope_id, target_az_angle, target_el_angle, created_at = db_select_command_output 30 | commands.append(Command( 31 | id=int(id), 32 | user_id=str(user_id), 33 | telescope_id=str(telescope_id), 34 | target_az_angle=float(target_az_angle), 35 | target_el_angle=float(target_el_angle), 36 | created_at=datetime.fromisoformat(str(created_at)) 37 | )) 38 | return commands 39 | def get_latest_command(telescope_id: int) -> Command: 40 | db = Database() 41 | _, db_select_command_outputs = db.read(table_name, criteria={'telescope_id': telescope_id}) 42 | if db_select_command_outputs: 43 | id, user_id, telescope_id, target_az_angle, target_el_angle, created_at = db_select_command_outputs[-1] 44 | return Command( 45 | id=int(id), 46 | user_id=str(user_id), 47 | telescope_id=str(telescope_id), 48 | target_az_angle=float(target_az_angle), 49 | target_el_angle=float(target_el_angle), 50 | created_at=datetime.fromisoformat(str(created_at)) 51 | ) 52 | else: 53 | raise HTTPException(status_code=404, detail=f"No commands found for telescope ID {telescope_id}.") 54 | 55 | 56 | def post_command(command: CreateCommand) -> Command: 57 | db = Database() 58 | _, _ = db.insert(table_name, command.__dict__) 59 | _, db_select_command_outputs = db.read(table_name, criteria=command.__dict__) 60 | id, user_id, telescope_id, target_az_angle, target_el_angle, created_at = db_select_command_outputs[-1] 61 | return Command( 62 | id=int(id), 63 | user_id=str(user_id), 64 | telescope_id=str(telescope_id), 65 | target_az_angle=float(target_az_angle), 66 | target_el_angle=float(target_el_angle), 67 | created_at=datetime.fromisoformat(str(created_at)) 68 | ) 69 | 70 | def delete_command(command_id: int) -> Command: 71 | db = Database() 72 | _, db_select_command_outputs = db.read(table_name, criteria={'id': command_id}) 73 | if db_select_command_outputs: 74 | id, user_id, telescope_id, target_az_angle, target_el_angle, created_at = db_select_command_outputs[0] 75 | command = Command( 76 | id=int(id), 77 | user_id=str(user_id), 78 | telescope_id=str(telescope_id), 79 | target_az_angle=float(target_az_angle), 80 | target_el_angle=float(target_el_angle), 81 | created_at=datetime.fromisoformat(str(created_at)) 82 | ) 83 | success, _ = db.delete(table_name, criteria={'id': command_id}) 84 | if success: 85 | return command 86 | else: 87 | raise HTTPException(status_code=500, detail="Failed to delete command.") 88 | else: 89 | raise HTTPException(status_code=404, detail=f"Command with ID {command_id} not found.") 90 | 91 | def update_command(command_id: int, command: Command) -> Command: 92 | db = Database() 93 | success, _ = db.update(table_name, criteria={'id': command_id}, data=command.__dict__) 94 | if success: 95 | _, db_select_command_outputs = db.read(table_name, criteria={'id': command_id}) 96 | id, user_id, telescope_id, target_az_angle, target_el_angle, created_at = db_select_command_outputs[0] 97 | return Command( 98 | id=int(id), 99 | user_id=str(user_id), 100 | telescope_id=str(telescope_id), 101 | target_az_angle=float(target_az_angle), 102 | target_el_angle=float(target_el_angle), 103 | created_at=datetime.fromisoformat(str(created_at)) 104 | ) 105 | else: 106 | raise HTTPException(status_code=500, detail="Failed to update command.") 107 | -------------------------------------------------------------------------------- /design/architecture.drawio: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /frontend/src/app/components/cam/cam.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { CanvasJSAngularChartsModule } from '@canvasjs/angular-charts'; 4 | import { FormsModule } from '@angular/forms'; 5 | import { CreateCommand } from '../../interfaces/create-command'; 6 | import { Command } from '../../interfaces/command'; 7 | import { CommandService } from '../../services/command.service'; 8 | import { getUser } from '../../signal'; 9 | import { ActivatedRoute } from '@angular/router'; 10 | 11 | @Component({ 12 | selector: 'app-cam', 13 | imports: [ 14 | CommonModule, 15 | CanvasJSAngularChartsModule, 16 | FormsModule, 17 | ], 18 | templateUrl: './cam.component.html', 19 | styleUrl: './cam.component.css' 20 | }) 21 | export class CamComponent { 22 | width = 360; // Width of the SVG 23 | height = 90; // Height of the SVG 24 | knobPosition: { x: number, y: number } = { x: 0, y: 0 }; 25 | isDragging = false; 26 | isPointing = true; 27 | isPointingPageActive = false; 28 | isLoading = false; 29 | azimuth = 180; // Initial azimuth value 30 | elevation = 45; // Initial elevation value 31 | latitude = 40.730610; // Sample latitude value 32 | longitude = -73.935242; // Sample longitude value 33 | altitude = 10; // Sample altitude value 34 | gridLines = this.generateGridLines(); 35 | sources = [ 36 | { id: 1, name: 'Sun' }, 37 | { id: 2, name: 'Moon' }, 38 | { id: 3, name: 'Mars' } 39 | ]; 40 | selectedSource: number = this.sources[0].id; 41 | telescopeId: number | null = null; 42 | 43 | chartData: { x: Date, y: number }[] = []; 44 | 45 | chartOptions = { 46 | animationEnabled: true, 47 | theme: "light2", 48 | title: { 49 | text: "Azimuth & Elevation Angle" 50 | }, 51 | axisX: { 52 | valueFormatString: "MMM", 53 | intervalType: "month", 54 | interval: 1 55 | }, 56 | axisY: { 57 | title: "Angle", 58 | suffix: "°" 59 | }, 60 | toolTip: { 61 | shared: true 62 | }, 63 | legend: { 64 | cursor: "pointer", 65 | itemclick: function(e: any){ 66 | if (typeof(e.dataSeries.visible) === "undefined" || e.dataSeries.visible) { 67 | e.dataSeries.visible = false; 68 | } else{ 69 | e.dataSeries.visible = true; 70 | } 71 | e.chart.render(); 72 | } 73 | }, 74 | data: [{ 75 | type:"line", 76 | name: "Azimuth", 77 | showInLegend: true, 78 | yValueFormatString: "#,###°F", 79 | dataPoints: [ 80 | { x: new Date(2021, 0, 1), y: 27 }, 81 | { x: new Date(2021, 1, 1), y: 28 }, 82 | { x: new Date(2021, 2, 1), y: 35 }, 83 | { x: new Date(2021, 3, 1), y: 45 }, 84 | { x: new Date(2021, 4, 1), y: 54 }, 85 | { x: new Date(2021, 5, 1), y: 64 }, 86 | { x: new Date(2021, 6, 1), y: 69 }, 87 | { x: new Date(2021, 7, 1), y: 68 }, 88 | { x: new Date(2021, 8, 1), y: 61 }, 89 | { x: new Date(2021, 9, 1), y: 50 }, 90 | { x: new Date(2021, 10, 1), y: 41 }, 91 | { x: new Date(2021, 11, 1), y: 33 } 92 | ] 93 | }, 94 | { 95 | type: "line", 96 | name: "Maximum", 97 | showInLegend: true, 98 | yValueFormatString: "#,###°F", 99 | dataPoints: [ 100 | { x: new Date(2021, 0, 1), y: 40 }, 101 | { x: new Date(2021, 1, 1), y: 42 }, 102 | { x: new Date(2021, 2, 1), y: 50 }, 103 | { x: new Date(2021, 3, 1), y: 62 }, 104 | { x: new Date(2021, 4, 1), y: 72 }, 105 | { x: new Date(2021, 5, 1), y: 80 }, 106 | { x: new Date(2021, 6, 1), y: 85 }, 107 | { x: new Date(2021, 7, 1), y: 84 }, 108 | { x: new Date(2021, 8, 1), y: 76 }, 109 | { x: new Date(2021, 9, 1), y: 64 }, 110 | { x: new Date(2021, 10, 1), y: 54 }, 111 | { x: new Date(2021, 11, 1), y: 44 } 112 | ] 113 | }] 114 | } 115 | 116 | constructor(private commandService: CommandService, private route: ActivatedRoute) { 117 | this.route.paramMap.subscribe(params => { 118 | const id = params.get('telescopeId'); 119 | this.telescopeId = id ? +id : null; 120 | }); 121 | } 122 | 123 | startDrag(event: MouseEvent): void { 124 | this.isDragging = true; 125 | this.updateKnobPosition(event); 126 | } 127 | 128 | stopDrag(): void { 129 | this.isDragging = false; 130 | this.updateAzimuthElevation(); 131 | } 132 | 133 | onMouseMove(event: MouseEvent): void { 134 | if (this.isDragging) { 135 | this.updateKnobPosition(event); 136 | } 137 | } 138 | 139 | point(): void { 140 | this.isPointing = true; 141 | this.updateAzimuthElevation(); 142 | } 143 | 144 | track(): void { 145 | this.isPointing = false; 146 | } 147 | 148 | startTracking(): void { 149 | // Implement tracking logic here 150 | console.log(`Started tracking source ${this.selectedSource}`); 151 | } 152 | 153 | startPointing(): void { 154 | this.isLoading = true; 155 | const createCommand: CreateCommand = { 156 | user_id: getUser()?.id ?? 0, 157 | telescope_id: this.telescopeId ?? 0, 158 | target_az_angle: this.azimuth, 159 | target_el_angle: this.elevation 160 | } 161 | 162 | this.commandService.postCommand(createCommand).subscribe({ 163 | next: (command: Command) => { 164 | console.log('Command sent successfully:', command); 165 | this.isLoading = false; 166 | this.isPointingPageActive = true; 167 | }, 168 | error: (error) => { 169 | console.error('Error sending command:', error); 170 | this.isLoading = false; 171 | } 172 | }); 173 | return; 174 | } 175 | 176 | private updateKnobPosition(event: MouseEvent): void { 177 | const container = event.target as HTMLElement; 178 | const rect = container.getBoundingClientRect(); 179 | const offsetX = event.clientX - rect.left; // Center the knob 180 | const offsetY = event.clientY - rect.top; // Center the knob 181 | 182 | const maxXOffset = rect.width / 2; // Max movement limit 183 | const maxYOffset = rect.height / 2; // Max movement limit 184 | const x = Math.max(-maxXOffset, Math.min(maxXOffset, offsetX)); 185 | const y = Math.max(-maxYOffset, Math.min(maxYOffset, offsetY)); 186 | 187 | this.knobPosition = { x, y }; 188 | this.updateAzimuthElevation(); 189 | } 190 | 191 | private updateAzimuthElevation(): void { 192 | this.azimuth = Math.round((this.knobPosition.x / this.width) * 360); 193 | this.elevation = Math.round((this.knobPosition.y / this.height) * 90); 194 | } 195 | 196 | private generateGridLines(): { x1: number, y1: number, x2: number, y2: number }[] { 197 | const gridLines = []; 198 | for (let i = 0; i <= 360; i += 30) { 199 | gridLines.push({ x1: i, y1: 0, x2: i, y2: 90 }); 200 | } 201 | for (let i = 0; i <= 90; i += 15) { 202 | gridLines.push({ x1: 0, y1: i, x2: 360, y2: i }); 203 | } 204 | return gridLines; 205 | } 206 | } -------------------------------------------------------------------------------- /backend/classes/database.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import os 3 | from dotenv import load_dotenv, set_key 4 | import sqlite3 5 | 6 | class Database: 7 | def __init__(self) -> None: 8 | load_dotenv() 9 | env = os.getenv('ENV', 'development') 10 | self.name = f"databases/{env}.db" 11 | self.conn = sqlite3.connect(self.name, check_same_thread=False) 12 | self.cur = self.conn.cursor() 13 | self.create_tables() 14 | def create_tables(self) -> None: 15 | self.cur.executescript(""" 16 | CREATE TABLE IF NOT EXISTS user( 17 | `id` INTEGER PRIMARY KEY AUTOINCREMENT, 18 | `email_address` TEXT UNIQUE, 19 | `password` TEXT, 20 | `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP 21 | ); 22 | 23 | CREATE TABLE IF NOT EXISTS telescope( 24 | `id` INTEGER PRIMARY KEY AUTOINCREMENT, 25 | `name` TEXT UNIQUE, 26 | `health_status` TEXT, 27 | `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP 28 | ); 29 | 30 | CREATE TABLE IF NOT EXISTS reading( 31 | `id` INTEGER PRIMARY KEY AUTOINCREMENT, 32 | `telescope_id` TEXT, 33 | `az_angle` DOUBLE, 34 | `el_angle` DOUBLE, 35 | `health_status` TEXT, 36 | `movement_status` TEXT, 37 | `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP 38 | ); 39 | 40 | CREATE TABLE IF NOT EXISTS position( 41 | `id` INTEGER PRIMARY KEY AUTOINCREMENT, 42 | `datetime` DATETIME, 43 | `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP 44 | ); 45 | 46 | CREATE TABLE IF NOT EXISTS command( 47 | `id` INTEGER PRIMARY KEY AUTOINCREMENT, 48 | `user_id` TEXT, 49 | `telescope_id` TEXT, 50 | `target_az_angle` DOUBLE, 51 | `target_el_angle` DOUBLE, 52 | `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP 53 | ); 54 | 55 | CREATE TABLE IF NOT EXISTS source( 56 | `id` INTEGER PRIMARY KEY AUTOINCREMENT, 57 | `name` TEXT UNIQUE 58 | ); 59 | """) 60 | self.conn.commit() 61 | 62 | def insert(self, table_name: str, data: dict) -> tuple[bool, list]: 63 | try: 64 | columns = ', '.join([f"`{key}`" for key in data.keys()]) 65 | placeholders = ', '.join(['?' for _ in data]) 66 | sql = f"INSERT INTO `{table_name}` ({columns}) VALUES ({placeholders})" 67 | self.cur.execute(sql, list(data.values())) 68 | self.conn.commit() 69 | return (True, [data]) 70 | except sqlite3.IntegrityError as e: 71 | print(f"Error inserting data: {e}") 72 | return (False, []) 73 | 74 | def delete(self, table_name: str, criteria: dict) -> tuple[bool, list]: 75 | try: 76 | where_clause = ' AND '.join([f"`{key}` = ?" for key in criteria]) 77 | sql = f"DELETE FROM `{table_name}` WHERE {where_clause}" 78 | self.cur.execute(sql, list(criteria.values())) 79 | self.conn.commit() 80 | return (True, [criteria]) 81 | except sqlite3.Error as e: 82 | print(f"Error deleting data: {e}") 83 | return (False, []) 84 | 85 | def read(self, table_name: str, criteria: dict = None, limit: int = None, offset: int = None) -> tuple[bool, list]: 86 | try: 87 | where_clause = ' AND '.join([f"`{key}` = ?" for key in criteria]) if criteria else "" 88 | limit_clause = f"LIMIT {limit}" if limit else "" 89 | offset_clause = f"OFFSET {offset}" if offset else "" 90 | sql = f"SELECT * FROM `{table_name}` " + \ 91 | (f"WHERE {where_clause} " if where_clause else "") + \ 92 | (f"{limit_clause} " if limit_clause else "") + \ 93 | (f"{offset_clause}" if offset_clause else "") 94 | self.cur.execute(sql, list(criteria.values()) if criteria else []) 95 | return (True, self.cur.fetchall()) 96 | except sqlite3.Error as e: 97 | print(f"Error reading data: {e}") 98 | return (False, []) 99 | 100 | def read_range(self, table_name: str, min_id: int, max_id: int) -> tuple[bool, list]: 101 | try: 102 | sql = f"SELECT * FROM `{table_name}` WHERE `id` BETWEEN ? AND ?" 103 | self.cur.execute(sql, (min_id, max_id)) 104 | return (True, self.cur.fetchall()) 105 | except sqlite3.Error as e: 106 | print(f"Error reading data: {e}") 107 | return (False, []) 108 | 109 | def update(self, table_name: str, data: dict, criteria: dict) -> tuple[bool, list]: 110 | try: 111 | set_clause = ', '.join([f"`{key}` = ?" for key in data]) 112 | where_clause = ' AND '.join([f"`{key}` = ?" for key in criteria]) 113 | sql = f"UPDATE `{table_name}` SET {set_clause} WHERE {where_clause}" 114 | self.cur.execute(sql, list(data.values()) + list(criteria.values())) 115 | self.conn.commit() 116 | return (True, [data]) 117 | except sqlite3.Error as e: 118 | print(f"Error updating data: {e}") 119 | return (False, []) 120 | 121 | def clear(self) -> tuple[bool, list]: 122 | if self.name not in ['production', 'development']: 123 | try: 124 | # Drop all tables 125 | self.cur.executescript(""" 126 | DROP TABLE IF EXISTS `user`; 127 | """) 128 | self.conn.commit() 129 | # Recreate tables 130 | self.create_tables() 131 | return (True, []) 132 | except sqlite3.Error as e: 133 | print(f"Error clearing the database: {e}") 134 | return (False, []) 135 | return (False, []) 136 | 137 | def main() -> None: 138 | from user import User 139 | from reading import Reading 140 | #load_dotenv() 141 | #set_key("", "testing") 142 | db = Database() 143 | print() 144 | 145 | # Insert new data 146 | db_insert_status, db_insert_output = db.insert('user', {'email_address': 'shlabisa@sa', 'password': 'pass'}) 147 | print("Insert", db_insert_status, db_insert_output) 148 | print() 149 | 150 | # Read users from db 151 | db_read_status, db_read_output = db.read('user') 152 | print("Read", db_read_status, db_read_output) 153 | print(*db_read_output[0]) 154 | id, email_address, password, created_at = db_read_output[0] 155 | print(User(id=1, 156 | email_address="snyide@gmail.com", 157 | password="snyide", 158 | created_at=datetime.now())) 159 | user = User(id= int(id), email_address =str(email_address), password=str(password), created_at= datetime.strptime(str(created_at), "%Y-%m-%d %H:%M:%S")) 160 | 161 | # Update user 162 | user_dict: dict = user.__dict__ 163 | user_dict["password"] = "pass-updated" 164 | db_update_status, db_update_output = db.update('user', user_dict, {'id': user.id}) 165 | print("Update", db_update_status, db_update_output) 166 | print() 167 | 168 | db_delete_status, db_delete_output = db.delete('user', {'id': user.id}) 169 | print("Delete", db_delete_status, db_delete_output) 170 | print() 171 | 172 | # Example usage: 173 | if __name__ == "__main__": 174 | main() -------------------------------------------------------------------------------- /backend/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, HTTPException 2 | from fastapi.middleware.cors import CORSMiddleware 3 | from classes.user import CreateUser, User 4 | from classes.telescope import CreateTelescope, Telescope 5 | from classes.reading import CreateReading, Reading 6 | from classes.source import CreateSource, Source 7 | from classes.command import CreateCommand, Command 8 | from classes.position import CreatePosition, Position 9 | from endpoints.users import get_users, post_user, get_user, auth_user, delete_user, update_user 10 | from endpoints.telescopes import get_telescopes, post_telescope, get_telescope, delete_telescope, update_telescope 11 | from endpoints.readings import get_reading, get_readings, post_reading, update_reading, delete_reading, get_latest_reading 12 | from endpoints.sources import get_sources, post_source, get_source, delete_source, update_source 13 | from endpoints.commands import get_commands, post_command, get_command, delete_command, update_command, get_latest_command 14 | from endpoints.positions import get_positions, post_position, get_position, delete_position, update_position 15 | import uvicorn 16 | 17 | BASE_URL = "/api/v1" 18 | app = FastAPI() 19 | 20 | origins = [ 21 | "http://localhost:4200", 22 | ] 23 | 24 | app.add_middleware( 25 | CORSMiddleware, 26 | allow_origins=origins, 27 | allow_credentials=True, # Allow cookies, authorization headers, etc. 28 | allow_methods=["*"], # Allow all HTTP methods (GET, POST, PUT, DELETE, etc.) 29 | allow_headers=["*"], # Allow all headers 30 | ) 31 | 32 | # curl -X GET -H "Content-Type: application/json" -d '{}' "http://127.0.0.1:8000/api/v1/users" 33 | @app.get(f"{BASE_URL}/users", response_model=list[User]) 34 | def get_users_endpoint(): 35 | return get_users() 36 | 37 | # curl -X POST -H "Content-Type: application/json" -d '{"email_address": "ska@sarao.ac.za", "password": "hlabisa"}' "http://127.0.0.1:8000/api/v1/users" 38 | @app.post(f"{BASE_URL}/users", response_model=User) 39 | def post_users_endpoint(user: CreateUser): 40 | return post_user(user) 41 | 42 | # curl -X GET -H "Content-Type: application/json" -d '{}' "http://127.0.0.1:8000/api/v1/users/1" 43 | @app.get(f"{BASE_URL}/users/{{user_id}}", response_model=User) 44 | def get_user_endpoint(user_id: int): 45 | return get_user(user_id) 46 | 47 | # curl -X POST -H "Content-Type: application/json" -d '{"email_address": "test@git.com", "password": "test123"}' "http://127.0.1:8000/api/v1/users/auth" 48 | @app.post(f"{BASE_URL}/users/auth", response_model=User) 49 | def auth_user_endpoint(user: CreateUser): 50 | return auth_user(user) 51 | 52 | # curl -X DELETE -H "Content-Type: application/json" -d '{}' "http://127.0.0.1:8000/api/v1/users/1" 53 | @app.delete(f"{BASE_URL}/users/{{user_id}}", response_model=User) 54 | def delete_user_endpoint(user_id: int): 55 | return delete_user(user_id) 56 | 57 | # curl -X PUT -H "Content-Type: application/json" -d '{"email_address": "new@email.com", "password": "newpassword"}' "http://127.0.0.1:8000/api/v1/users/1" 58 | @app.put(f"{BASE_URL}/users/{{user_id}}", response_model=User) 59 | def update_user_endpoint(user_id: int, user: User): 60 | return update_user(user_id, user) 61 | 62 | """Telescope""" 63 | 64 | # curl -X GET -H "Content-Type: application/json" -d '{}' "http://127.0.0.1:8000/api/v1/telescopes" 65 | @app.get(f"{BASE_URL}/telescopes", response_model=list[Telescope]) 66 | def get_telescopes_endpoint(): 67 | return get_telescopes() 68 | 69 | # curl -X POST -H "Content-Type: application/json" -d '{"email_address": "ska@sarao.ac.za", "password": "hlabisa"}' "http://127.0.0.1:8000/api/v1/telescopes" 70 | @app.post(f"{BASE_URL}/telescopes", response_model=Telescope) 71 | def post_telescopes_endpoint(telescope: CreateTelescope): 72 | return post_telescope(telescope) 73 | 74 | # curl -X GET -H "Content-Type: application/json" -d '{}' "http://127.0.0.1:8000/api/v1/telescopes/1" 75 | @app.get(f"{BASE_URL}/telescopes/{{telescope_id}}", response_model=Telescope) 76 | def get_telescope_endpoint(telescope_id: int): 77 | return get_telescope(telescope_id) 78 | 79 | # curl -X DELETE -H "Content-Type: application/json" -d '{}' "http://127.0.0.1:8000/api/v1/telescopes/1" 80 | @app.delete(f"{BASE_URL}/telescopes/{{telescope_id}}", response_model=Telescope) 81 | def delete_telescope_endpoint(telescope_id: int): 82 | return delete_telescope(telescope_id) 83 | 84 | # curl -X PUT -H "Content-Type: application/json" -d '{"email_address": "new@email.com", "password": "newpassword"}' "http://127.0.0.1:8000/api/v1/telescopes/1" 85 | @app.put(f"{BASE_URL}/telescopes/{{telescope_id}}", response_model=Telescope) 86 | def update_telescope_endpoint(telescope_id: int, telescope: Telescope): 87 | return update_telescope(telescope_id, telescope) 88 | 89 | """Reading""" 90 | 91 | # curl -X GET -H "Content-Type: application/json" -d '{}' "http://127.0.0.1:8000/api/v1/readings" 92 | @app.get(f"{BASE_URL}/readings", response_model=list[Reading]) 93 | def get_readings_endpoint(): 94 | return get_readings() 95 | 96 | # curl -X POST -H "Content-Type: application/json" -d '{"email_address": "ska@sarao.ac.za", "password": "hlabisa"}' "http://127.0.0.1:8000/api/v1/telescopes" 97 | @app.post(f"{BASE_URL}/readings", response_model=Reading) 98 | def post_readings_endpoint(reading: CreateReading): 99 | return post_reading(reading) 100 | 101 | # curl -X GET -H "Content-Type: application/json" -d '{}' "http://127.0.0.1:8000/api/v1/readings/1" 102 | @app.get(f"{BASE_URL}/readings/{{reading_id}}", response_model=Reading) 103 | def get_reading_endpoint(reading_id: int): 104 | return get_reading(reading_id) 105 | 106 | @app.get(f"{BASE_URL}/readings/{{telescope_id}}/latest", response_model=Reading) 107 | def get_latest_reading_endpoint(telescope_id: int): 108 | return get_latest_reading(telescope_id) 109 | 110 | # curl -X DELETE -H "Content-Type: application/json" -d '{}' "http://127.0.0.1:8000/api/v1/readings/1" 111 | @app.delete(f"{BASE_URL}/readings/{{reading_id}}", response_model=Reading) 112 | def delete_reading_endpoint(reading_id: int): 113 | return delete_reading(reading_id) 114 | 115 | # curl -X PUT -H "Content-Type: application/json" -d '{"email_address": "new@email.com", "password": "newpassword"}' "http://127.0.0.1:8000/api/v1/telescopes/1" 116 | @app.put(f"{BASE_URL}/readings/{{reading_id}}", response_model=Reading) 117 | def update_reading_endpoint(reading_id: int, telescope: Reading): 118 | return update_reading(reading_id, telescope) 119 | 120 | """Source""" 121 | 122 | @app.get(f"{BASE_URL}/sources", response_model=list[Source]) 123 | def get_sources_endpoint(): 124 | return get_sources() 125 | 126 | @app.post(f"{BASE_URL}/sources", response_model=Source) 127 | def post_source_endpoint(source: CreateSource): 128 | return post_source(source) 129 | 130 | @app.get(f"{BASE_URL}/sources/{{source_id}}", response_model=Source) 131 | def get_source_endpoint(source_id: int): 132 | return get_source(source_id) 133 | 134 | @app.delete(f"{BASE_URL}/sources/{{source_id}}", response_model=Source) 135 | def delete_source_endpoint(source_id: int): 136 | return delete_source(source_id) 137 | 138 | @app.put(f"{BASE_URL}/sources/{{source_id}}", response_model=Source) 139 | def update_source_endpoint(source_id: int, source: Source): 140 | return update_source(source_id, source) 141 | 142 | """Command""" 143 | 144 | @app.get(f"{BASE_URL}/commands", response_model=list[Command]) 145 | def get_commands_endpoint(): 146 | return get_commands() 147 | 148 | @app.post(f"{BASE_URL}/commands", response_model=Command) 149 | def post_command_endpoint(command: CreateCommand): 150 | return post_command(command) 151 | 152 | @app.get(f"{BASE_URL}/commands/{{command_id}}", response_model=Command) 153 | def get_command_endpoint(command_id: int): 154 | return get_command(command_id) 155 | 156 | @app.get(f"{BASE_URL}/commands/{{telescope_id}}/latest", response_model=Command) 157 | def get_latest_command_endpoint(telescope_id: int): 158 | return get_latest_command(telescope_id) 159 | 160 | @app.delete(f"{BASE_URL}/commands/{{command_id}}", response_model=Command) 161 | def delete_command_endpoint(command_id: int): 162 | return delete_command(command_id) 163 | 164 | @app.put(f"{BASE_URL}/commands/{{command_id}}", response_model=Command) 165 | def update_command_endpoint(command_id: int, command: Command): 166 | return update_command(command_id, command) 167 | 168 | """Position""" 169 | 170 | @app.get(f"{BASE_URL}/positions", response_model=list[Position]) 171 | def get_positions_endpoint(): 172 | return get_positions() 173 | 174 | @app.post(f"{BASE_URL}/positions", response_model=Position) 175 | def post_position_endpoint(position: CreatePosition): 176 | return post_position(position) 177 | 178 | @app.get(f"{BASE_URL}/positions/{{position_id}}", response_model=Position) 179 | def get_position_endpoint(position_id: int): 180 | return get_position(position_id) 181 | 182 | @app.delete(f"{BASE_URL}/positions/{{position_id}}", response_model=Position) 183 | def delete_position_endpoint(position_id: int): 184 | return delete_position(position_id) 185 | 186 | @app.put(f"{BASE_URL}/positions/{{position_id}}", response_model=Position) 187 | def update_position_endpoint(position_id: int, position: Position): 188 | return update_position(position_id, position) 189 | 190 | 191 | def main() -> None: 192 | uvicorn.run("main:app", host='127.0.0.1', port=8000, workers=2, reload=True) 193 | #uvicorn.run(app, host="127.0.0.1", port=8000) 194 | 195 | if __name__ == "__main__": 196 | main() -------------------------------------------------------------------------------- /design/database.drawio: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | --------------------------------------------------------------------------------