Please see the installation guide for a guide on how to install the project.
- Git Tutorial - UVic Rocketry Git reference guide
- Software Process - UVic Rocketry Software Process guidelines
- Ground Support - Presentation on high level overview
With the Docker engine running, start the Ground Support containers by running the following command in your terminal:
$ docker compose upYou will see the logs from the containers. To shut the containers down, hit ctl-C in the terminal.
To start the containers in detached (background) mode, which will hide the container logs, add the --detach flag to the command above. If the containers are running in detached mode, you can stop them with the following command:
$ docker stop <container-name>Where container-name can be found by running docker ps.
See the screenshot below for an example of starting the containers in detached mode, and inspecting the names of the the containers. In this screenshot, react-client is a container name.
To attach a terminal (ash) to a running container, use the following command:
$ docker exec -it <container-name> ashSee the Docker CLI and Compose CLI documentation here for more information.
There is a dev-tools Docker compose profile that can be run in addition to the default (unspecified) option. This profile builds a mongo-express container for inspecting and modifying the local mongo database instance. To run docker compose with this profile, enter the following in your terminal:
$ docker compose --profile dev-tools upOnce running, to access the mongo-express client, visit localhost:8081 in your browser.
⚠️ 4 tab indentation ⚠️ Please change your IDE setting to match or I will hunt you down.
- Use
PascalCasefor component file names - Use
kebab-casefor view file names - Use
camelCasefor all variable names - Use
PascalCasefor all interface names - Use
UPPER_CASEfor all constants
- Use
snake_casefor all variable names - Use
PascalCasefor all class names - Use
UPPER_CASEfor all constants
Use minimal commenting. If you need to comment your code either your code is too complex or is not readable. Comment is only needed for complex algorithms or for code that is not self explanatory.
- Use
//for single line comments and/* */for multi line comments.
- Use
import React from 'react'for importing React - Use
import { useState } from 'react'for importing hooks - Use
import { IProps } from './component'for importing interfaces - Use
import { useSocketContext } from '../context/socket'for importing custom hooks
All interface names should be PascalCase with an I at the start of the name. For example IProps or IUser.
- Use
kebab-casefor all branch names
Pull requests should have a title that is descriptive of the changes made. The description should be a list of changes made and a brief explanation of why the changes were made.
Create a new file in the components folder with the name of the component in PascalCase. The file is named in PascalCase and the extension should be .tsx as we are using TypeScript. Use the following template to create a component:
import React, { useEffect } from 'react';
interface IProps {
// props
}
const Component: React.FC<IProps> = (props: IProps) => {
// state
const [data, setData] = useState<any>(null);
// or
const useSocketContext = () => useContext<SocketContext>(Context);
// effects
useEffect(() => {
// Perform any initializations or side effects here
// This will only run once when the component is mounted
return () => {
// Perform any cleanup here
// This will only run once when the component is unmounted
};
}, []);
// render
return (
<>
</>
);
};
export default Component;There should be an accompanying documentation file in the documentation/components folder with the same name as the component. This file should contain a description of the component and how to use it. Use this template: Example Component Document.
Once your component is finished add it to the index.d.ts file in the components folder. This will allow you to import the component from the components folder without having to specify the file name. For example:
import { ExampleComponent, IExampleProps } from '../components';rather than
import { ExampleComponent } from '../components/exampleComponent';Create a new file in the views folder with the name of the view in kebab-case. The file is named in kebab-case and the extension should be .tsx as we are using TypeScript. Use the following template to create a view:
import React, { useEffect } from 'react';
import { Grid } from '@material-ui/core';
import { Header } from '../../components';
interface IProps {
// props
}
const View: React.FC<IProps> = (props: IProps) => {
const { } = props;
// state
const [data, setData] = useState<any>(null);
// or
const useSocketContext = () => useContext<SocketContext>(Context);
// render
return (
<Grid
container
direction="column"
paddingX="2rem"
paddingY="2rem"
gap={3}
>
<Grid container>
<Header breadCrumbs={breadCrumbs} />
</Grid>
<Grid item>
// View content goes here
</Grid>
</Grid>
);
};The helper file api.ts is give typesafety and security to ground supports api from the frontend. The file is located in src/services/api.ts. To learn more about the API please see the API Documentation.
// Import the entity + verb you specifically want to access
import { ... } from "../utils/api.ts";
// If you GETTing a value from the api you also most likely need the populated version of the entity
import { ...Populated } from "../utils/api.ts";Example
getting a rocket from the API updating a value in the rocket and then sending it back to the API.
import { IRocketPopulated } from "../utils/";
import { getRocket, updateRocket } from "../services/api.ts";
import { useState, useEffect } from "react";
const [rocketData, setRocketData] = useState<IRocketPopulated | null>(null);
async retrieveRocket = () => {
const rocketResponse = await getRocket(1);
const rocketData = rocketResponse.data as IRocketPopulated;
return rocketData;
}
// Retrieve the rocket data called when the component is mounted
useEffect(() => {
const rocketData = await retrieveRocket();
setRocketData(rocketData);
}, []);
// Update the rocket data
setRocketData({
...rocketData,
name: "New Rocket Name"
});
const success = await updateRocket(1, rocketData).error.status === 200;We have two contexts that are currently being used. The first is the SocketContext which is used to access the socket. The second is the activeMissionContext which is used to access the rocket and mission data of an active flight. To access the context use the following template:
For the server there are three main things that need to be created. The first is an entity which is the object that is stored in the database. The second is a controller which is the logic that is used to interact with the entity. The third is a route which is the endpoint that is used to access the controller.
import mongoose, { Document, Schema, Types } from "mongoose";
export interface IEntity {
// entity properties
Name: string;
};
export interface IEntityModel extends IEntity, Document { };
export const EntitySchema = new Schema(
{
// entity properties
Name: {
type: String,
required: true,
validator: (value: string) => {
// validation logic
}
},
},
{
versionKey: false,
timestamps: true
}
);
export const Entity = mongoose.model<IEntityModel>("Entity", EntitySchema);For basic CRUD operations we will just be using the Generic controller. This controller is located in src/controllers/Generic.ts.
import express from "express";
import controller from "../controllers/Generic";
import Entity from "../models/Entity";
const router = express.Router();
router.get("/", controller.getAll(Entity));
router.get("/:id", controller.getById(Entity));
router.post("/", controller.create(Entity));
router.patch("/:id", controller.update(Entity));
router.delete("/:id", controller.remove(Entity));
export default router;
