Skip to content

Commit

Permalink
Feature/schiltz3/db migrate (#48)
Browse files Browse the repository at this point in the history
# Migrate database to clean up structure & rewrite database calls to
match

Edit the structure

* CODE => NAME
* TITLE
* INFO
* ~~ROLE_NAME~~
* ~~CHANNEL_NAME~~
* ROLE_ID
* CHANNEL_ID
* ~~UUID~~
* \+ DUPE
* \+ ACTIVE

This amounts to:
* Removing `UUID`, `ROLE_NAME` and `CHANNEL_NAME`.
* Cleaning up `CODE` and replacing it with `NAME`.
* Adding the `DUPE` field to signify if a class is a duplicate.
* Adding `ACTIVE` to specify if the course is being offered that
semester. (Currently, all classes are considered active.)

### Steps to migrate database and discord server
Run `migrateDb` command to migrate the database
Run `CreateChannels` command to update channel names from the database
Run `CreateRoles` command to update role names from the database

# Link to github cards
#33 
#35 
#49 
#50

# Additions

* Add a helper function to create embeds
* Add `migrateDB` command to update the database to the new schema,
update how duplicate codes are handled, and clean all strings.
* Add modified JS Docs to function. (Types are not added since this is
specified in function definitions with typescript)

# Changes

* Change db scheme
* Change role and channel db calls to reflect changes
* Change how duplicate classes are handled
* Use course title and info in channel topics



## Bugfix
* replace all contiguous whitespace with a dash instead of replacing
spaces with a dash
* Replace contiguous dashes with a dash in ROLE_NAME and CHANNEL_NAME
* Trim leading and trailing whitespace from TITLE
* Limit role name and channel name to 100 characters each
* Limit channel topic to 1024 characters each
* Only reply to role selection if role is actually assigned
* Add bottleneck package to limit the bot to 50 API calls per second
* Prefers matching on channel ID's rather than names
  • Loading branch information
schiltz3 committed Jan 18, 2023
2 parents a07bb49 + 606276e commit f3a0597
Show file tree
Hide file tree
Showing 23 changed files with 718 additions and 152 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@
*.env
node_modules
unused-commands
d.py/
d.py/

# Ignore specific dev commands not allowed in production
commands/owner/deleteChannels.ts
commands/owner/deleteRoles.ts
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@ The bot should become responsive. You can check for errors in the console.
- [Discord.js Docs](https://discord.js.org/#/docs)
- [Discord.js Guide](https://discordjs.guide/)
- [WOKcommands Docs](https://docs.wornoffkeys.com/)
- [AntaresBot Codebase](https://playantares.com/antaresbot)
- [Antares Status Tracking](https://status.playantares.com/)
- [AntaresBot Codebase](https://antaresnetwork.com/antaresbot)
- [Antares Status Tracking](https://status.antaresnetwork.com/)

## Credits

Expand Down
138 changes: 129 additions & 9 deletions __tests__/utils/channels.tests.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { cleanChannelString } from "../../src/utils/channels";
import { expect, describe, it } from "@jest/globals";
import {
cleanChannelString,
concatCategoryName,
moveChannel,
getCourseName,
getTopic,
} from "../../src/utils/channels";
import { expect, describe, it, beforeEach } from "@jest/globals";
import { Guild, GuildChannel } from "discord.js";
import { IClass } from "../../src/models/classModel";

//cleanChannelString

describe("cleanChannelString", () => {
it("should lowercase the string", () => {
expect(cleanChannelString("ABCD")).toEqual("abcd");
Expand All @@ -12,13 +19,14 @@ describe("cleanChannelString", () => {
expect(cleanChannelString("a!b@c#d$")).toEqual("abcd");
});

it('should replace "compsci " with "cs"', () => {
expect(cleanChannelString("compsci ")).toEqual("cs");
it("should replace whitespace with a hyphen", () => {
expect(cleanChannelString("a b c")).toEqual("a-b-c");
});

it("should replace spaces and parentheses with hyphens", () => {
expect(cleanChannelString("a b (c)")).toEqual("a-b-c");
it("should remove consecutive hyphens", () => {
expect(cleanChannelString("a--b--c")).toBe("a-b-c");
});

it("should return an empty string for an empty input", () => {
expect(cleanChannelString("")).toBe("");
});
Expand All @@ -27,7 +35,119 @@ describe("cleanChannelString", () => {
expect(cleanChannelString("!@#$%^&*")).toBe("");
});

it("should handle a mix of all transformations", () => {
expect(cleanChannelString("Compsci a%$ 123!")).toEqual("csa-123");
it("should truncate the string to 100 characters", () => {
expect(cleanChannelString("a".repeat(200))).toHaveLength(100);
});
});

describe("getTopic", () => {
const default_string = "This is a test";
let course: IClass = {
TITLE: default_string,
INFO: default_string,
id: default_string,
NAME: default_string,
DUPE: false,
ACTIVE: true,
ROLE_NAME: default_string,
ROLE_ID: default_string,
CHANNEL_ID: default_string,
};
const extra_characters = 3;
beforeEach(() => {
course = {
TITLE: default_string,
INFO: default_string,
id: default_string,
NAME: default_string,
DUPE: false,
ACTIVE: true,
ROLE_NAME: default_string,
ROLE_ID: default_string,
CHANNEL_ID: default_string,
};
});

it("should return a truncated string of the concatenated title and info", () => {
course.TITLE = "Introduction to Mathematics";
expect(getTopic(course)).toHaveLength(
course.TITLE.length + course.INFO.length + extra_characters
);
});

it("should not return a string longer than 1024 characters", () => {
course.TITLE = "a".repeat(600);
course.INFO = "b".repeat(600);
expect(getTopic(course)).toHaveLength(1024);
});

it("should return a string of length 1024 if TITLE is 1025 characters", () => {
course.TITLE = "a".repeat(1025);
course.INFO = "";
expect(getTopic(course)).toHaveLength(1024);
});

it("should return a string of length 1024 if INFO is 1025 characters", () => {
course.TITLE = "";
course.INFO = "b".repeat(1025);
expect(getTopic(course)).toHaveLength(1024);
});
});

// getCourseName
describe("getCourseName", () => {
const default_string = "This is a test";
it("should return the course name if DUPE is false", () => {
const course: IClass = {
id: default_string,
NAME: "Math 101",
TITLE: "Introduction to Mathematics",
INFO: default_string,
DUPE: false,
ACTIVE: true,
ROLE_NAME: default_string,
ROLE_ID: default_string,
CHANNEL_ID: default_string,
};
expect(getCourseName(course)).toBe("Math 101");
});

it("should return the concatenated course name and title if DUPE is true", () => {
const course: IClass = {
id: default_string,
NAME: "Math 101",
TITLE: "Introduction to Mathematics",
INFO: default_string,
DUPE: true,
ACTIVE: true,
ROLE_NAME: default_string,
ROLE_ID: default_string,
CHANNEL_ID: default_string,
};
expect(getCourseName(course)).toBe("Math 101-Introduction to Mathematics");
});
});

//moveChannel
// Can only test pure functionality
describe("moveChannel", () => {
it("should not move the channel if it is already in the specified category", async () => {
// Set up the test so that the channel is already in the specified category
const guild = {} as Guild;
const category_name = "abcd";
const channel = { parent: { name: category_name } } as GuildChannel;
// Ensure that the channel was not moved
expect(await moveChannel(guild, channel, category_name)).toBe(0);
});
});

//concatCategoryName
describe("concatCategoryName", () => {
it("should return the category name if the category number is 0", () => {
expect(concatCategoryName("abcd", 0)).toBe("abcd");
});

it("should return the category name and number if the category number is not 0", () => {
expect(concatCategoryName("abcd", 1)).toBe("abcd 1");
});
});
40 changes: 40 additions & 0 deletions __tests__/utils/roles.tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { expect, describe, it } from "@jest/globals";
import { cleanRoleString } from "../../src/utils/roles";

//cleanRoleString
describe("cleanRoleString", () => {
it("should lowercase the string", () => {
expect(cleanRoleString("ABCD")).toEqual("abcd");
});

it("should remove special characters", () => {
expect(cleanRoleString("a!b@c#d$")).toEqual("abcd");
});

it("should remove newlines", () => {
expect(cleanRoleString("\na\n")).toEqual("a");
});

it("should replace whitespace with a hyphen", () => {
expect(cleanRoleString("a b c")).toEqual("a-b-c");
});

it("should remove consecutive hyphens", () => {
expect(cleanRoleString("a--b--c")).toBe("a-b-c");
});

it("should return an empty string for an empty input", () => {
expect(cleanRoleString("")).toBe("");
});

it("cleanRoleString should return an empty string for a string with only special characters", () => {
expect(cleanRoleString("!@#$%^&*")).toBe("");
});

it("should truncate the string to 100 characters", () => {
expect(cleanRoleString("a".repeat(200))).toHaveLength(100);
});
it("should be able to handle a comboination of everything", () => {
expect(cleanRoleString("a! -- b@c\n#d$e%")).toEqual("a-bcde");
});
});
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"homepage": "https://github.com/Antares-Network/CSSC-Bot#readme",
"dependencies": {
"axios": "^0.27.2",
"bottleneck": "^2.19.5",
"chalk": "4.1.2",
"discord.js": "^13.9.2",
"mongoose": "^6.6.0",
Expand Down
27 changes: 21 additions & 6 deletions src/commands/owner/csClassPoll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,19 @@ import {
import chalk from "chalk";
import { ICommand } from "wokcommands";
import { classModel, IClass } from "../../models/classModel";
import { checkForRoles } from "../../utils/roles";
import { checkForRoles, cleanRoleString } from "../../utils/roles";
import { sleep } from "../../utils/sleep";
import { getCourseName } from "../../utils/channels";

// Splits any size list into lists of at most `max_list_len`
/**
* @description - Splits a list into lists of at most `max_list_len` elements
* @author John Schiltz
* @template T
* @param list - The list to split
* @param max_list_len - The maximum length of each list
* @return {*}
*/
function split_list<T>(list: T[], max_list_len: number): T[][] {
const class_chunks: T[][] = [];
for (let i = 0; i < list.length; i += max_list_len) {
Expand All @@ -19,11 +28,17 @@ function split_list<T>(list: T[], max_list_len: number): T[][] {
return class_chunks;
}

// consumes a Class and returns Message Selec tOption data
/**
* @description - Creates a MessageSelectOptionData object from a class object
* @author John Schiltz
* @param _class - The class to create the option from
* @return {*}
*/
function create_option_from_class(_class: IClass): MessageSelectOptionData {
const clean_name = cleanRoleString(getCourseName(_class));
return {
label: _class.CODE,
value: _class.CODE,
label: clean_name,
value: clean_name,
description: _class.TITLE,
};
}
Expand All @@ -49,7 +64,7 @@ export default {
return;
}

const classes = await classModel.find({}).sort({ CODE: 1 });
const classes = await classModel.find({}).sort({ NAME: 1 });
const class_chunks = split_list(classes, 25);

const rows: MessageActionRow[] = [];
Expand Down Expand Up @@ -80,7 +95,7 @@ export default {
)
.setFooter({
text: `Delivered in: ${client.ws.ping}ms | CSSC-Bot | ${process.env.VERSION}`,
iconURL: "https://playantares.com/resources/CSSC-bot/icon.jpg",
iconURL: "https://antaresnetwork.com/resources/CSSC-bot/icon.jpg",
});

msgInt.reply({ embeds: [infoEmbed], components: row_chunks[index] });
Expand Down
Loading

0 comments on commit f3a0597

Please sign in to comment.