Skip to content

Commit

Permalink
Merge pull request #104 from Carifio24/solar-eclipse-part-2
Browse files Browse the repository at this point in the history
Updates to solar eclipse endpoint
  • Loading branch information
Carifio24 authored Mar 8, 2024
2 parents a77f326 + 29774ea commit eb15117
Show file tree
Hide file tree
Showing 11 changed files with 984 additions and 786 deletions.
1,564 changes: 839 additions & 725 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"start": "npm run serve"
},
"dependencies": {
"@effect/schema": "^0.63.2",
"@types/body-parser": "^1.19.2",
"@types/cookie-parser": "^1.4.2",
"@types/cors": "^2.8.12",
Expand All @@ -14,6 +15,8 @@
"@types/express-session": "^1.17.4",
"@types/jsonwebtoken": "^8.5.8",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^7.1.1",
"@typescript-eslint/parser": "^7.1.1",
"body-parser": "^1.19.1",
"connect-session-sequelize": "^7.1.3",
"cookie-parser": "^1.4.6",
Expand All @@ -27,15 +30,13 @@
"mysql2": "^2.3.3",
"nanoid": "^3.3.2",
"path": "^0.12.7",
"sequelize": "^6.21.3",
"sequelize": "^6.37.1",
"sha3": "^2.1.4",
"typescript": "^4.6.3",
"typescript": "^5.4.2",
"uuid": "^8.3.2",
"winston": "^3.10.0"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.19.0",
"@typescript-eslint/parser": "^5.19.0",
"eslint": "^8.13.0",
"eslint-plugin-vue": "^8.6.0",
"sass": "~1.32.0",
Expand Down
4 changes: 2 additions & 2 deletions src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export type LoginResponse = {

export type CreateClassResponse = {
result: CreateClassResult;
class?: object;
class?: object | undefined;
}

// Grab any environment variables
Expand Down Expand Up @@ -313,7 +313,7 @@ async function checkLogin<T extends Model & User>(email: string, password: strin
}
return {
result: result,
id: user?.id,
id: user?.id ?? 0,
success: LoginResult.success(result)
};
}
Expand Down
2 changes: 1 addition & 1 deletion src/models/dashboard_class_group.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Sequelize, DataTypes, Model, InferAttributes, InferCreationAttributes, CreationOptional, DATE } from "sequelize";
import { Sequelize, DataTypes, Model, InferAttributes, InferCreationAttributes, CreationOptional } from "sequelize";

export class DashboardClassGroup extends Model<InferAttributes<DashboardClassGroup>, InferCreationAttributes<DashboardClassGroup>> {
declare id: CreationOptional<number>;
Expand Down
80 changes: 56 additions & 24 deletions src/stories/solar-eclipse-2024/database.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,78 @@
import * as S from "@effect/schema/Schema";
import { cosmicdsDB } from "../../database";
import { logger } from "../../logger";
import {
isArrayThatSatisfies,
isNumberArray,
} from "../../utils";

import { initializeModels, SolarEclipse2024Response } from "./models";
import { UpdateAttributes } from "../../utils";
import { initializeModels, SolarEclipse2024Data } from "./models";

type SolarEclipse2024UpdateAttributes = UpdateAttributes<SolarEclipse2024Data>;

initializeModels(cosmicdsDB);

export interface SolarEclipse2024Data {
user_uuid: string;
user_selected_locations: [number, number][],
timestamp: Date
}
const LatLonArray = S.mutable(S.array(S.mutable(S.tuple(S.number, S.number))));

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function isValidSolarEclipseData(data: any): data is SolarEclipse2024Response {
return typeof data.user_uuid === "string" &&
isArrayThatSatisfies(data.user_selected_locations, (arr) => {
return arr.every(x => isNumberArray(x) && x.length === 2);
});
}
export const SolarEclipse2024Entry = S.struct({
user_uuid: S.string,
user_selected_locations: LatLonArray,
cloud_cover_selected_locations: LatLonArray,
info_time_ms: S.optional(S.number.pipe(S.int()), { exact: true }),
app_time_ms: S.optional(S.number.pipe(S.int()), { exact: true }),
});

export async function submitSolarEclipse2024Response(data: SolarEclipse2024Response): Promise<SolarEclipse2024Response | null> {
export const SolarEclipse2024Update = S.struct({
user_selected_locations: S.optional(LatLonArray, { exact: true }),
cloud_cover_selected_locations: S.optional(LatLonArray, { exact: true }),
delta_info_time_ms: S.optional(S.number.pipe(S.int()), { exact: true }),
delta_app_time_ms: S.optional(S.number.pipe(S.int()), { exact: true }),
});

export type SolarEclipse2024DataT = S.Schema.To<typeof SolarEclipse2024Entry>;
export type SolarEclipse2024UpdateT = S.Schema.To<typeof SolarEclipse2024Update>;

export async function submitSolarEclipse2024Data(data: SolarEclipse2024DataT): Promise<SolarEclipse2024Data | null> {
logger.verbose(`Attempting to submit solar eclipse 2024 measurement for user ${data.user_uuid}`);

const dataWithCounts = {
...data,
user_selected_locations_count: data.user_selected_locations.length
user_selected_locations_count: data.user_selected_locations.length,
cloud_cover_selected_locations_count: data.cloud_cover_selected_locations.length,
};

return SolarEclipse2024Response.upsert(dataWithCounts).then(([item, _]) => item);
return SolarEclipse2024Data.upsert(dataWithCounts).then(([item, _]) => item);
}

export async function getAllSolarEclipse2024Responses(): Promise<SolarEclipse2024Response[]> {
return SolarEclipse2024Response.findAll();
export async function getAllSolarEclipse2024Data(): Promise<SolarEclipse2024Data[]> {
return SolarEclipse2024Data.findAll();
}

export async function getSolarEclipse2024Response(userUUID: string): Promise<SolarEclipse2024Response | null> {
return SolarEclipse2024Response.findOne({
export async function getSolarEclipse2024Data(userUUID: string): Promise<SolarEclipse2024Data | null> {
return SolarEclipse2024Data.findOne({
where: { user_uuid: userUUID }
});
}

export async function updateSolarEclipse2024Data(userUUID: string, update: SolarEclipse2024UpdateT): Promise<boolean> {
const data = await SolarEclipse2024Data.findOne({ where: { user_uuid: userUUID } });
if (data === null) {
return false;
}
const dbUpdate: SolarEclipse2024UpdateAttributes = {};
if (update.user_selected_locations) {
const selected = data.user_selected_locations.concat(update.user_selected_locations);
dbUpdate.user_selected_locations = selected;
dbUpdate.user_selected_locations_count = selected.length;
}
if (update.cloud_cover_selected_locations) {
const selected = data.cloud_cover_selected_locations.concat(update.cloud_cover_selected_locations);
dbUpdate.cloud_cover_selected_locations = selected;
dbUpdate.cloud_cover_selected_locations_count = selected.length;
}
if (update.delta_info_time_ms) {
dbUpdate.info_time_ms = data.info_time_ms + update.delta_info_time_ms;
}
if (update.delta_app_time_ms) {
dbUpdate.app_time_ms = data.app_time_ms + update.delta_app_time_ms;
}
const result = await data.update(dbUpdate);
return result !== null;
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { Sequelize, DataTypes, Model, InferAttributes, InferCreationAttributes, CreationOptional } from "sequelize";

export class SolarEclipse2024Response extends Model<InferAttributes<SolarEclipse2024Response>, InferCreationAttributes<SolarEclipse2024Response>> {
export class SolarEclipse2024Data extends Model<InferAttributes<SolarEclipse2024Data>, InferCreationAttributes<SolarEclipse2024Data>> {
declare id: CreationOptional<number>;
declare user_uuid: string;
declare user_selected_locations: [number, number][];
declare user_selected_locations_count: number;
declare cloud_cover_selected_locations: [number, number][];
declare cloud_cover_selected_locations_count: number;
declare info_time_ms: CreationOptional<number>;
declare app_time_ms: CreationOptional<number>;
declare timestamp: CreationOptional<Date>;
}

export function initializeSolarEclipse2024ResponseModel(sequelize: Sequelize) {
SolarEclipse2024Response.init({
export function initializeSolarEclipse2024DataModel(sequelize: Sequelize) {
SolarEclipse2024Data.init({
id: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
Expand All @@ -29,13 +33,31 @@ export function initializeSolarEclipse2024ResponseModel(sequelize: Sequelize) {
type: DataTypes.INTEGER,
allowNull: false
},
cloud_cover_selected_locations: {
type: DataTypes.JSON,
allowNull: false
},
cloud_cover_selected_locations_count: {
type: DataTypes.INTEGER,
allowNull: false
},
info_time_ms: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0
},
app_time_ms: {
type: DataTypes.INTEGER,
allowNull: false,
defaultValue: 0
},
timestamp: {
type: DataTypes.DATE,
allowNull: false,
defaultValue: Sequelize.literal("CURRENT_TIMESTAMP")
}
}, {
sequelize,
engine: "InnoDB"
engine: "InnoDB",
});
}
6 changes: 3 additions & 3 deletions src/stories/solar-eclipse-2024/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Sequelize } from "sequelize";
import { SolarEclipse2024Response, initializeSolarEclipse2024ResponseModel } from "./eclipse_response";
import { SolarEclipse2024Data, initializeSolarEclipse2024DataModel } from "./eclipse_data";

export {
SolarEclipse2024Response
SolarEclipse2024Data
};

export function initializeModels(db: Sequelize) {
initializeSolarEclipse2024ResponseModel(db);
initializeSolarEclipse2024DataModel(db);
}
58 changes: 46 additions & 12 deletions src/stories/solar-eclipse-2024/router.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,71 @@
import * as S from "@effect/schema/Schema";
import * as Either from "effect/Either";
import { Router } from "express";
import { getAllSolarEclipse2024Responses, getSolarEclipse2024Response, isValidSolarEclipseData, submitSolarEclipse2024Response } from "./database";
import {
getAllSolarEclipse2024Data,
getSolarEclipse2024Data,
submitSolarEclipse2024Data,
SolarEclipse2024Entry,
updateSolarEclipse2024Data,
SolarEclipse2024Update,
} from "./database";

const router = Router();

router.put("/response", async (req, res) => {
router.put("/data", async (req, res) => {
const data = req.body;
const valid = isValidSolarEclipseData(data);
const maybe = S.decodeUnknownEither(SolarEclipse2024Entry)(data);

if (!valid) {
if (Either.isLeft(maybe)) {
res.status(400);
res.json({ error: "Malformed response submission" });
res.json({ error: "Malformed data submission" });
return;
}

const response = await submitSolarEclipse2024Response(data);
const response = await submitSolarEclipse2024Data(maybe.right);
if (response === null) {
res.status(400);
res.json({ error: "Error creating solar eclipse 2024 response" });
res.json({ error: "Error creating solar eclipse 2024 entry" });
return;
}

res.json({ response });
});

router.get("/responses", async (_req, res) => {
const responses = await getAllSolarEclipse2024Responses();
router.get("/data", async (_req, res) => {
const responses = await getAllSolarEclipse2024Data();
res.json({ responses });
});

router.get("/response/:userUUID", async (req, res) => {
const uuid = req.params.userUUID as string;
const response = await getSolarEclipse2024Response(uuid);
router.get("/data/:uuid", async (req, res) => {
const uuid = req.params.uuid as string;
const response = await getSolarEclipse2024Data(uuid);
res.json({ response });
});

router.patch("/data/:uuid", async (req, res) => {
const uuid = req.params.uuid as string;
const data = req.body;

const maybe = S.decodeUnknownEither(SolarEclipse2024Update)(data);
if (Either.isLeft(maybe)) {
res.status(400).json({ error: "Malformed update submission" });
return;
}

const response = await getSolarEclipse2024Data(uuid);
if (response === null) {
res.status(404).json({ error: "Specified user data does not exist" });
return;
}

const success = await updateSolarEclipse2024Data(uuid, maybe.right);
if (!success) {
res.status(500).json({ error: "Error updating user data" });
return;
}
res.json({ response });

});

export default router;

This file was deleted.

5 changes: 5 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import { nanoid } from "nanoid";
import { enc, SHA256 } from "crypto-js";
import { v5 } from "uuid";

import { Model } from "sequelize";

// This type describes objects that we're allowed to pass to a model's `update` method
export type UpdateAttributes<M extends Model> = Parameters<M["update"]>[0];

export function createVerificationCode(): string {
return nanoid(21);
}
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
"exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
Expand Down

0 comments on commit eb15117

Please sign in to comment.