Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updating Offline Mode #447

Merged
merged 32 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
fa7f6cb
Im embarassingly proud of this
BanEvading Feb 11, 2025
a13e152
Mostly works, issue with assigning alliance status
BanEvading Feb 13, 2025
456d00f
This should fix it
BanEvading Feb 13, 2025
313a6aa
changes
BanEvading Feb 14, 2025
72467d4
FIXXED IT!
BanEvading Feb 14, 2025
a1cc6a7
Added dynamic text for alliances vs teams
BanEvading Feb 14, 2025
63c70d7
Fixed Typos
BanEvading Feb 14, 2025
dd03965
Further added dynamic text for alliances
BanEvading Feb 14, 2025
9ebf5aa
1.1.21
gearbox4026 Feb 14, 2025
9fd4941
Merge branch 'main' into alliances
renatodellosso Feb 14, 2025
d469613
Removed console import
BanEvading Feb 14, 2025
8f2928b
Merge branch 'alliances' of https://github.com/Decatur-Robotics/Gearb…
BanEvading Feb 14, 2025
1e445d0
Fixed Build and Unit Test issues
BanEvading Feb 14, 2025
771b0b5
Fixed Formatting
BanEvading Feb 14, 2025
12aa658
Fixed Formatting using Prettier
BanEvading Feb 14, 2025
0236ac2
Merge pull request #445 from Decatur-Robotics/alliances
BanEvading Feb 14, 2025
8188b34
Better speedtest
renatodellosso Feb 16, 2025
15a7d9f
Remove "not allied" and "allied" from createTeam page
renatodellosso Feb 16, 2025
820262a
Use CachedDb
renatodellosso Feb 16, 2025
29fd2b0
Add cache stats page and fix pit reports
renatodellosso Feb 16, 2025
991a333
Format numbers in cache stats
renatodellosso Feb 16, 2025
3d3bf31
Fix team loading in stats page
renatodellosso Feb 17, 2025
8b29ceb
Fix picklists overwriting before they load
renatodellosso Feb 17, 2025
cf7020b
Fix pit report comment removal in scouter management
renatodellosso Feb 17, 2025
880a127
1.2.2
gearbox4026 Feb 17, 2025
3302ce6
Merge branch 'main' into cache
renatodellosso Feb 17, 2025
cb14117
Complete save picklists deps array
renatodellosso Feb 17, 2025
78c4261
Disable verbose logging for cache
renatodellosso Feb 17, 2025
fa15cca
Merge pull request #446 from Decatur-Robotics/cache
renatodellosso Feb 17, 2025
13ca31a
Increased cache TTL
renatodellosso Feb 17, 2025
1fc4345
Add dev index, fix camel case for serializeDatabaseObject[s]
renatodellosso Feb 18, 2025
caa1731
Fix build issue with missing SMTP password
renatodellosso Feb 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion components/Container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,8 @@ export default function Container(props: ContainerProps) {

<Link href={`/${selectedTeam?.slug}`}>
<button className="btn btn-ghost normal-case bg-base-100">
<BiHome className="text-2xl"></BiHome>Team Home
<BiHome className="text-2xl"></BiHome>
{selectedTeam?.alliance ? "Alliance Home" : "Team Home"}
</button>
</Link>

Expand Down
5 changes: 3 additions & 2 deletions components/TeamCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ export default function TeamCard(props: { team: Team | undefined }) {
className="w-full bg-base-300 border-4 border-base-300 transition ease-in hover:border-primary"
>
<h1 className="font-semibold max-sm:text-sm">
{team?.league} Team <span className="text-accent">{team?.number}</span>{" "}
- <span className="text-primary">{team?.users.length}</span> members
{team?.league} {team?.alliance ? "Alliance" : "Team"}{" "}
<span className="text-accent">{team?.number}</span> -{" "}
<span className="text-primary">{team?.users.length}</span> members
</h1>
</Card>
);
Expand Down
18 changes: 12 additions & 6 deletions components/stats/Picklist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import {
const SHOW_PICKLISTS_ON_TEAM_CARDS = false;
const SHOW_CARD_IDS = false;

const api = new ClientApi();

function TeamCard(props: {
entry: PicklistEntry;
draggable: boolean;
Expand Down Expand Up @@ -297,8 +299,6 @@ export function TeamList(props: {
);
}

const api = new ClientApi();

export default function PicklistScreen(props: {
teams: number[];
reports: Report[];
Expand All @@ -321,10 +321,16 @@ export default function PicklistScreen(props: {
const teams = props.teams.map((team) => ({ number: team }));

// Save picklists
useEffect(
() => savePicklistGroup(props.picklist._id, picklists, strikethroughs, api),
[props.picklist._id, picklists, strikethroughs],
);
useEffect(() => {
if (loadingPicklists !== LoadState.Loaded) return;
savePicklistGroup(props.picklist._id, picklists, strikethroughs, api);
}, [
props.picklist._id,
picklists,
strikethroughs,
LoadState.Loaded,
loadingPicklists,
]);

const updatePicklist = useCallback(
(picklist: Picklist) => {
Expand Down
9 changes: 7 additions & 2 deletions lib/MongoDB.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import DbInterface, {
WithStringOrObjectIdId,
} from "./client/dbinterfaces/DbInterface";
import { default as BaseMongoDbInterface } from "mongo-anywhere/MongoDbInterface";
import CachedDbInterface from "./client/dbinterfaces/CachedDbInterface";
import { cacheOptions } from "./client/dbinterfaces/CachedDbInterface";

if (!process.env.MONGODB_URI) {
// Necessary to allow connections from files running outside of Next
Expand All @@ -28,10 +30,13 @@ clientPromise = global.clientPromise;

export { clientPromise };

export async function getDatabase(): Promise<MongoDBInterface> {
export async function getDatabase(): Promise<DbInterface> {
if (!global.interface) {
await clientPromise;
const dbInterface = new MongoDBInterface(clientPromise);
const dbInterface = new CachedDbInterface(
new MongoDBInterface(clientPromise),
cacheOptions,
);
await dbInterface.init();
global.interface = dbInterface;

Expand Down
30 changes: 19 additions & 11 deletions lib/ResendUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@ import { Resend } from "resend";
import { getDatabase } from "./MongoDB";
import { User } from "./Types";
import CollectionId from "./client/CollectionId";

const resend = new Resend(process.env.SMTP_PASSWORD);
import { ObjectId } from "bson";

export interface ResendInterface {
createContact: (rawUser: NextAuthUser) => Promise<void>;
emailDevelopers: (subject: string, message: string) => void;
}

export class ResendUtils implements ResendInterface {
private static resend: Resend;

constructor() {
ResendUtils.resend ??= new Resend(process.env.SMTP_PASSWORD);
}

async createContact(rawUser: NextAuthUser) {
const user = rawUser as User;

Expand All @@ -26,7 +31,7 @@ export class ResendUtils implements ResendInterface {

const nameParts = user.name?.split(" ");

const res = await resend.contacts.create({
const res = await ResendUtils.resend.contacts.create({
email: user.email,
firstName: nameParts[0],
lastName: nameParts.length > 1 ? nameParts[1] : "",
Expand All @@ -41,13 +46,16 @@ export class ResendUtils implements ResendInterface {
}

const db = await getDatabase();
// Going around our own interface is a red flag, but it's 11 PM and I'm tired -Renato
db.db
?.collection(CollectionId.Users)
.updateOne(
{ email: user.email },
{ $set: { resendContactId: res.data.id } },
);
const id = (await db.findObject(CollectionId.Users, { email: user.email }))
?._id;
if (!id) {
console.error("User not found in database", user.email);
return;
}

db.updateObjectById(CollectionId.Users, new ObjectId(id), {
resendContactId: res.data.id,
});
}

async emailDevelopers(subject: string, message: string) {
Expand All @@ -56,7 +64,7 @@ export class ResendUtils implements ResendInterface {
return;
}

resend.emails.send({
ResendUtils.resend.emails.send({
from: "Gearbox Server <[email protected]>",
to: JSON.parse(process.env.DEVELOPER_EMAILS), // Environment variables are always strings, so we need to parse it
subject,
Expand Down
3 changes: 2 additions & 1 deletion lib/TheBlueAlliance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import { NotLinkedToTba } from "./client/ClientUtils";
import { GameId, defaultGameId } from "./client/GameId";
import { games } from "./games";
import DbInterface from "./client/dbinterfaces/DbInterface";

export namespace TheBlueAlliance {
export interface SimpleTeam {
Expand Down Expand Up @@ -209,7 +210,7 @@ export namespace TheBlueAlliance {

export class Interface {
req: Request;
db: Promise<MongoDBInterface>;
db: Promise<DbInterface>;

competitionPairings: CompetitonNameIdPair[] = [];

Expand Down
3 changes: 3 additions & 0 deletions lib/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export class Team {
tbaId: string | undefined;
number: number;
league: League = League.FRC;
alliance: boolean;

owners: string[];
users: string[];
Expand All @@ -109,6 +110,7 @@ export class Team {
tbaId: string | undefined,
number: number,
league: League = League.FRC,
alliance: boolean = false,
owners: string[] = [],
users: string[] = [],
scouters: string[] = [],
Expand All @@ -122,6 +124,7 @@ export class Team {
this.tbaId = tbaId;
this.number = number;
this.league = league;
this.alliance = alliance;
this.owners = owners;
this.users = users;
this.scouters = scouters;
Expand Down
14 changes: 7 additions & 7 deletions lib/UrlResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export interface ResolvedUrlData {
* @param object - Any `Object` with a `_id` property
* @returns - The same object, but with `_id` set as a string
*/
export function SerializeDatabaseObject(object: any): any {
export function serializeDatabaseObject(object: any): any {
if (!object) {
return null;
}
Expand All @@ -46,8 +46,8 @@ export function SerializeDatabaseObject(object: any): any {
return object;
}

export function SerializeDatabaseObjects(objectArray: any[]): any[] {
return objectArray.map((obj) => SerializeDatabaseObject(obj));
export function serializeDatabaseObjects(objectArray: any[]): any[] {
return objectArray.map((obj) => serializeDatabaseObject(obj));
}

/**
Expand Down Expand Up @@ -115,10 +115,10 @@ export default async function UrlResolver(
// find these slugs, and convert them to a JSON safe condition
// if they dont exist, simply return nothing
const data: ResolvedUrlData = {
team: SerializeDatabaseObject(await promises[0]),
season: SerializeDatabaseObject(await promises[1]),
competition: SerializeDatabaseObject(await promises[2]),
report: SerializeDatabaseObject(await promises[3]),
team: serializeDatabaseObject(await promises[0]),
season: serializeDatabaseObject(await promises[1]),
competition: serializeDatabaseObject(await promises[2]),
report: serializeDatabaseObject(await promises[3]),
};
return data;
} catch (error) {
Expand Down
33 changes: 31 additions & 2 deletions lib/api/ClientApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ export default class ClientApi extends NextApiTemplate<ApiDependencies> {
});

createTeam = createNextRoute<
[string, string, number, League],
[string, string, number, League, boolean],
Team | undefined,
ApiDependencies,
void
Expand All @@ -233,7 +233,7 @@ export default class ClientApi extends NextApiTemplate<ApiDependencies> {
res,
{ db: dbPromise, resend, userPromise },
authData,
[name, tbaId, number, league],
[name, tbaId, number, league, alliance],
) => {
const user = (await userPromise)!;
const db = await dbPromise;
Expand All @@ -256,6 +256,7 @@ export default class ClientApi extends NextApiTemplate<ApiDependencies> {
tbaId,
number,
league,
alliance,
[user._id!.toString()],
[user._id!.toString()],
[user._id!.toString()],
Expand Down Expand Up @@ -2302,4 +2303,32 @@ export default class ClientApi extends NextApiTemplate<ApiDependencies> {
res.status(200).send(responseObj);
},
});

getCacheStats = createNextRoute<
[],
object | undefined,
ApiDependencies,
void
>({
isAuthorized: AccessLevels.IfDeveloper,
handler: async (req, res, {}, authData, args) => {
if (!global.cache) return res.status(200).send(undefined);
const stats = global.cache.getStats();
return res.status(200).send(stats);
},
});

getCachedValue = createNextRoute<
[string],
object | undefined,
ApiDependencies,
void
>({
isAuthorized: AccessLevels.IfDeveloper,
handler: async (req, res, {}, authData, [key]) => {
if (!global.cache) return res.status(500).send({ error: "No cache" });
const val = global.cache.get(key) as object | undefined;
return res.status(200).send(val);
},
});
}
66 changes: 66 additions & 0 deletions lib/client/dbinterfaces/CachedDbInterface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { ObjectId } from "bson";
import CollectionId, { CollectionIdToType } from "@/lib/client/CollectionId";
import DbInterface, {
WithStringOrObjectIdId,
} from "@/lib/client/dbinterfaces/DbInterface";
import { default as BaseCachedDbInterface } from "mongo-anywhere/CachedDbInterface";
import NodeCache from "node-cache";
import { CacheOperation } from "mongo-anywhere/CachedDbInterface";

export const cacheOptions: NodeCache.Options = {
stdTTL: 10 * 60,
useClones: false,
};

export default class CachedDbInterface
extends BaseCachedDbInterface<CollectionId, CollectionIdToType<CollectionId>>
implements DbInterface
{
init(): Promise<void> {
return super.init(Object.values(CollectionId));
}
getTtl(operation: CacheOperation, collectionId: CollectionId): number {
if (operation === "count") return 3 * 60 * 60;

return cacheOptions.stdTTL!;
}
addObject<TId extends CollectionId, TObj extends CollectionIdToType<TId>>(
collection: TId,
object: WithStringOrObjectIdId<TObj>,
): Promise<TObj> {
return super.addObject(collection, object);
}
deleteObjectById(collection: CollectionId, id: ObjectId): Promise<void> {
return super.deleteObjectById(collection, id);
}
updateObjectById<
TId extends CollectionId,
TObj extends CollectionIdToType<TId>,
>(collection: TId, id: ObjectId, newValues: Partial<TObj>): Promise<void> {
return super.updateObjectById(collection, id, newValues);
}
findObjectById<
TId extends CollectionId,
TObj extends CollectionIdToType<TId>,
>(collection: TId, id: ObjectId): Promise<TObj | undefined> {
return super.findObjectById(collection, id);
}
findObject<TId extends CollectionId, TObj extends CollectionIdToType<TId>>(
collection: TId,
query: object,
): Promise<TObj | undefined> {
return super.findObject(collection, query);
}
findObjects<TId extends CollectionId, TObj extends CollectionIdToType<TId>>(
collection: TId,
query: object,
): Promise<TObj[]> {
return super.findObjects(collection, query);
}
countObjects(
collection: CollectionId,
query: object,
): Promise<number | undefined> {
return super.countObjects(collection, query);
}
}
Loading
Loading