Skip to content

Commit cfe6c46

Browse files
authored
Merge pull request #17 from petercort/add-wax-utilities
Add wax utilities
2 parents 5cb1fdf + e53b6ba commit cfe6c46

19 files changed

+929
-388
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
# Changelog
22

33

4+
### Version 1.0.0
5+
6+
#### feature
7+
* PR [#17](https://github.com/petercort/FBF-Buddy/pull/17) - Add wax utilities
8+
9+
10+
411
### Version 0.0.5
512

613
#### feature

config/event-buddy-deployment.yaml

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: event-buddy
5+
spec:
6+
replicas: 1
7+
revisionHistoryLimit: 3
8+
selector:
9+
matchLabels:
10+
app: event-buddy
11+
template:
12+
metadata:
13+
labels:
14+
app: event-buddy
15+
spec:
16+
containers:
17+
- image: ghcr.io/petercort/event-buddy:0.0.5
18+
name: event-buddy
19+
ports:
20+
- containerPort: 80
21+
volumeMounts:
22+
- name: azure-secrets-store
23+
mountPath: "/mnt/secrets-store"
24+
readOnly: true
25+
volumes:
26+
- name: azure-secrets-store
27+
csi:
28+
driver: secrets-store.csi.k8s.io
29+
readOnly: true
30+
volumeAttributes:
31+
secretProviderClass: "sc-demo-keyvault-csi"

package-lock.json

+341-374
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@
1616
"license": "MIT",
1717
"dependencies": {
1818
"@sequelize/postgres": "^7.0.0-alpha.43",
19+
"axios": "^1.7.9",
1920
"discord-interactions": "^4.0.0",
2021
"discord.js": "^14.16.3",
21-
"dotenv": "^16.4.5",
22+
"dotenv": "^16.4.7",
2223
"express": "^4.18.2",
2324
"fbf-event-buddy": "file:",
2425
"sequelize": "^6.37.5",
2526
"sqlite3": "^5.1.7"
2627
},
2728
"devDependencies": {
28-
"nodemon": "^3.0.0"
29+
"nodemon": "^3.1.9"
2930
}
3031
}

src/app.js

+9-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const fs = require('node:fs');
22
const path = require('node:path');
33
const { Client, Collection, Events, GatewayIntentBits } = require('discord.js');
44
const { Op } = require('sequelize');
5-
const { EventsTable } = require('./dbObjects.js');
5+
const { EventsTable, UsersTable, BikesTable } = require('./dbObjects.js');
66
const { exec } = require('node:child_process');
77
const { execute } = require('./commands/utility/create_event.js');
88
const discordToken = fs.readFileSync("/mnt/secrets-store/discordToken", 'utf8');
@@ -31,6 +31,10 @@ for (const folder of commandFolders) {
3131
client.once(Events.ClientReady, readyClient => {
3232
console.log('Syncing database...');
3333
EventsTable.sync({ alter: true });
34+
//UsersTable.sync({ alter: true, force: true });
35+
UsersTable.sync();
36+
//BikesTable.sync({ alter: true, force: true });
37+
BikesTable.sync();
3438
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
3539
});
3640

@@ -55,8 +59,7 @@ client.on(Events.InteractionCreate, async interaction => {
5559
}
5660
});
5761

58-
try {
59-
client.login(discordToken);
60-
} catch (error) {
61-
console.error(error);
62-
}
62+
// Start the Strava webhook server
63+
require('./strava_webhook.js');
64+
65+
client.login(discordToken);

src/commands.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const foldersPath = path.join(__dirname, 'commands');
1212
const commandFolders = fs.readdirSync(foldersPath);
1313

1414
for (const folder of commandFolders) {
15+
console.log(`[INFO] Processing commands in folder: ${folder}`);
1516
// Grab all the command files from the commands directory you created earlier
1617
const commandsPath = path.join(foldersPath, folder);
1718
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
@@ -40,7 +41,10 @@ const rest = new REST().setToken(discordToken);
4041
Routes.applicationGuildCommands(appId, guildId),
4142
{ body: commands },
4243
);
43-
44+
await rest.put(
45+
Routes.applicationCommands(appId),
46+
{ body: commands },
47+
);
4448
console.log(`Successfully reloaded ${data.length} application (/) commands.`);
4549
} catch (error) {
4650
// And of course, make sure you catch and log any errors!
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const { SlashCommandBuilder } = require('@discordjs/builders');
2+
const { EmbedBuilder } = require('discord.js');
3+
const axios = require('axios');
4+
const { UsersTable } = require('../../dbObjects.js'); // Assuming you have a UsersTable to store user data
5+
const fs = require('node:fs');
6+
const path = require('node:path');
7+
// load strava configuration
8+
const STRAVA_CLIENT_ID = fs.readFileSync("/mnt/secrets-store/STRAVA_CLIENT_ID", 'utf8');
9+
const STRAVA_REDIRECT_URI = fs.readFileSync("/mnt/secrets-store/STRAVA_REDIRECT_URI", 'utf8');
10+
11+
12+
module.exports = {
13+
data: new SlashCommandBuilder()
14+
.setName('connect_strava')
15+
.setDescription('Connect your Strava account to collect ride data.'),
16+
async execute(interaction) {
17+
const userId = interaction.user.id;
18+
const stravaAuthUrl = `https://www.strava.com/oauth/authorize?client_id=${STRAVA_CLIENT_ID}&response_type=code&redirect_uri=${STRAVA_REDIRECT_URI}/${userId}&scope=read,activity:read_all,profile:read_all`;
19+
20+
const embed = new EmbedBuilder()
21+
.setTitle('Connect Strava')
22+
.setDescription(`[Click here to connect your Strava account](${stravaAuthUrl})`)
23+
.setColor('#FC4C02');
24+
25+
await interaction.reply({ embeds: [embed], ephemeral: true });
26+
27+
// Save the user ID to the database to track the connection process
28+
await UsersTable.upsert({ userId, strava_connected: false });
29+
},
30+
};

src/commands/utility/get_all_bikes.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const { SlashCommandBuilder } = require('discord.js');
2+
const { BikesTable } = require('../../dbObjects.js');
3+
module.exports = {
4+
data: new SlashCommandBuilder()
5+
.setName('get_all_bikes')
6+
.setDescription('Get all your bikes!'),
7+
async execute(interaction) {
8+
const userId = interaction.user.id;
9+
10+
try {
11+
// Query the BikesTable to get all bikes for the user
12+
const bikes = await BikesTable.findAll({ where: { userId } });
13+
14+
if (bikes.length === 0) {
15+
return await interaction.reply('No bikes found.');
16+
}
17+
const bikeList = bikes.map(bike => `${bike.name} (${bike.brand} ${bike.model})`).join('\n');
18+
return await interaction.reply(`Your bikes:\n${bikeList}`);
19+
} catch (error) {
20+
console.error('Error fetching bikes:', error);
21+
return await interaction.reply('There was an error fetching your bikes.');
22+
}
23+
},
24+
};
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const { SlashCommandBuilder } = require('discord.js');
2+
const { BikesTable } = require('../../dbObjects.js');
3+
module.exports = {
4+
data: new SlashCommandBuilder()
5+
.setName('get_bike_by_name')
6+
.setDescription('Get a bike by it\'s name!')
7+
.addStringOption(option =>
8+
option.setName('name')
9+
.setDescription('The name of the bike')
10+
.setRequired(true)),
11+
async execute(interaction) {
12+
const userId = interaction.user.id;
13+
const bikeName = interaction.options.getString('name')
14+
try {
15+
// Query the BikesTable to get the bike by name for the user
16+
const bike = await BikesTable.findOne({ where: { userId, name: bikeName } });
17+
18+
if (!bike) {
19+
return await interaction.reply('No bike found with that name.');
20+
}
21+
const miles = Math.round(bike.lastWaxedDistance * 0.000621371); // equivilant meters to miles
22+
const bikeInfo = `Bike information:\nName: ${bike.name}\nBrand: ${bike.brand}\nModel: ${bike.model}.\nCurrent Mileage: ${Math.round(bike.distance * 0.000621371)}\nThis chain was last waxed on ${bike.lastWaxedDate} at ${miles} miles.`;
23+
24+
return await interaction.reply(bikeInfo);
25+
} catch (error) {
26+
console.error('Error fetching bike:', error);
27+
return await interaction.reply('There was an error fetching your bike.');
28+
}
29+
}
30+
};

src/commands/utility/get_last_ride.js

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const { SlashCommandBuilder } = require('@discordjs/builders');
2+
const { EmbedBuilder } = require('discord.js');
3+
const axios = require('axios');
4+
const getStravaAuthentication = require('../../shared_library/strava_authentication.js');
5+
6+
module.exports = {
7+
data: new SlashCommandBuilder()
8+
.setName('get_latest_ride')
9+
.setDescription('Get your latest ride from Strava.'),
10+
async execute(interaction) {
11+
const userId = interaction.user.id;
12+
13+
// do some logic here to determine if hte access token is expired if so refresh it and then use it
14+
const strava_access_token = await getStravaAuthentication(userId);
15+
if (!strava_access_token) {
16+
return interaction.reply({ content: 'You need to connect your Strava account first.', ephemeral: true });
17+
}
18+
try {
19+
const response = await axios.get('https://www.strava.com/api/v3/athlete/activities', {
20+
headers: { Authorization: `Bearer ${strava_access_token}` },
21+
params: { per_page: 1 }
22+
});
23+
24+
const latestRide = response.data[0];
25+
26+
if (!latestRide) {
27+
return interaction.reply({ content: 'No rides found.', ephemeral: true });
28+
}
29+
30+
const embed = new EmbedBuilder()
31+
.setTitle('Latest Ride')
32+
.setDescription(`**Distance:** ${latestRide.distance} meters\n**Date:** ${new Date(latestRide.start_date).toLocaleString()}`)
33+
.setColor('#FC4C02');
34+
35+
return interaction.reply({ embeds: [embed], ephemeral: true });
36+
} catch (error) {
37+
console.error('Error fetching latest ride:', error);
38+
return interaction.reply({ content: 'There was an error fetching your latest ride.', ephemeral: true });
39+
}
40+
},
41+
};
+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const { SlashCommandBuilder } = require('@discordjs/builders');
2+
const { BikesTable } = require('../../dbObjects.js');
3+
4+
module.exports = {
5+
data: new SlashCommandBuilder()
6+
.setName('i_waxed_my_chain')
7+
.setDescription('Update the date of when you last waxed your chain for a specific bike.')
8+
.addStringOption(option =>
9+
option.setName('bike_name')
10+
.setDescription('The name of the bike')
11+
.setRequired(true))
12+
.addStringOption(option =>
13+
option.setName('date')
14+
.setDescription('The date you last waxed your chain (YYYY-MM-DD). If nothing is entered, assuming today.')
15+
.setRequired(false))
16+
.addStringOption(option =>
17+
option.setName('mileage')
18+
.setDescription('The mileage you waxed your chain at. If nothing is entered, assuming current mileage.')
19+
.setRequired(false)),
20+
async execute(interaction) {
21+
const userId = interaction.user.id;
22+
const bikeName = interaction.options.getString('bike_name');
23+
// if date is null use today
24+
let date = interaction.options.getString('date')
25+
if (!interaction.options.getString('date')) {
26+
const currentDate = new Date();
27+
const month = currentDate.getMonth() + 1; // Months are zero-based, so add 1
28+
const day = currentDate.getDate();
29+
const year = currentDate.getFullYear();
30+
date = `${month}/${day}/${year}`;
31+
}
32+
const bike = await BikesTable.findOne({ where: { userId: userId, name: bikeName } });
33+
var mileage = ""
34+
if (interaction.options.getString('mileage')) {
35+
mileage = interaction.options.getString('mileage') * 1609.344;
36+
} else {
37+
mileage = bike.distance;
38+
}
39+
40+
try {
41+
const output = await BikesTable.update(
42+
{ lastWaxedDate: date, lastWaxedDistance: mileage},
43+
{ where: { userId: userId, bikeId: bike.bikeId } }
44+
);
45+
const distanceMiles = Math.round(mileage * 0.000621371192);
46+
await interaction.reply({ content: `Successfully updated the last waxed date for bike ID ${bike.bikeId} to ${date}, at ${distanceMiles} miles.`});
47+
} catch (error) {
48+
console.error('Error updating waxed chain date:', error);
49+
await interaction.reply({ content: 'There was an error updating the waxed chain date.', ephemeral: true });
50+
}
51+
},
52+
};

src/commands/utility/reload.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const { SlashCommandBuilder } = require('discord.js');
2+
3+
module.exports = {
4+
category: 'utility',
5+
data: new SlashCommandBuilder()
6+
.setName('reload')
7+
.setDescription('Reloads a command.')
8+
.addStringOption(option =>
9+
option.setName('command')
10+
.setDescription('The command to reload.')
11+
.setRequired(true)),
12+
async execute(interaction) {
13+
const commandName = interaction.options.getString('command', true).toLowerCase();
14+
const command = interaction.client.commands.get(commandName);
15+
16+
if (!command) {
17+
return interaction.reply(`There is no command with name \`${commandName}\`!`);
18+
}
19+
20+
delete require.cache[require.resolve(`../${command.category}/${command.data.name}.js`)];
21+
22+
try {
23+
const newCommand = require(`../${command.category}/${command.data.name}.js`);
24+
interaction.client.commands.set(newCommand.data.name, newCommand);
25+
await interaction.reply(`Command \`${newCommand.data.name}\` was reloaded!`);
26+
} catch (error) {
27+
console.error(error);
28+
await interaction.reply(`There was an error while reloading a command \`${command.data.name}\`:\n\`${error.message}\``);
29+
}
30+
},
31+
};

0 commit comments

Comments
 (0)