Hello, Blog
If you have just stumbled upon my OSD600 series of blog posts, it has been created to document and share my learnings as I progress through my Open Source Development college course.
In this blog post, I’ll share my progress on the course's final assignment, Release 0.4.
A bit about Release 0.4
For our final assignment, we were tasked to leverage the skills and experience we’ve built over the term to work on something meaningful to us in one way or another.
If you missed how I started working on 0.4, check out my previous post
Where am I at right now?
At this stage, I’ve already made significant progress. I’ve gotten a hang of the stack, picked up two issues, closed one, and created a draft Pull Request for the second.
The Stack
move-fast-and-break-things / aibyss
🚧 UNDER DEVELOPMENT 🚧 Aibyss: code your AI to compete in a survival game
🚧 UNDER DEVELOPMENT 🚧 Aibyss: code your AI to compete in a survival game
Setup
- install node.js
-
(on macOS only) install
node-gyp
:npm install -g node-gyp
- install the dependencies:
npm ci
Development server
Start the development server on http://localhost:3000
:
npm run dev
Create new user
npm run create-user <username>
Running the tests
unit tests
npm run test
e2e tests
First, setup e2e tests by running npm run test:e2e:install
, then run the tests with:
npm run test:e2e
Contributors guide
We follow conventional commits, name your PRs accordingly
Production
Build the application for production:
npm run build
Locally preview production build:
npm run preview
Check out the deployment documentation for more information.
The project I’m contributing to, AIbyss, uses various tools and technologies, many of which were entirely new to me. Familiarizing myself with the stack was one of the goals I set for Release 0.4, so I took the time to explore the project structure and dive into the documentation for each tool and framework.
Now, I have a basic understanding of building Vue.js
apps!
The key components of the stack:
-
Nuxt- a powerful open-source framework built on top of
Vue.js
, designed for creating dynamic full-stack web applications. It provides a great way to build full-stackVue
apps.
-
Vue.js - a "progressive JavaScript framework" focused on building user interfaces.
Vue.js
piqued my interest for a while, and after finally using it in this project, I can see why it’s so popular in the developer community - it's both intuitive and flexible. - Prisma - a modern Object-Relational Mapping (ORM) tool for simplifying database interactions. Having heard much about it, I am excited to incorporate it into future projects.
My contributions (as of now)
If you’ve read my first post, you’ll know that my ultimate goal was to contribute to the project in a meaningful way. To achieve this, I focused on taking and resolving issues labelled as high priority.
So far, I’ve successfully closed one issue and created a draft Pull Request for the second, marking steady progress toward my goal.
Issue 1 - Making the Rating Page publicly accessible
feat: let the users view the rating page without having to log in #42
This is a follow-up idea to the #37 and should be done after it is merged.
When I first picked up this issue, I thought it would be straightforward since the repo maintainer provided clear guidance on where to start. However, it turned out to be more challenging than expected. The task involved a lot of debugging and required a deeper understanding of how routing and middleware were set up in the project.
Comment for #42
Hi @arilloid! 👋
Thank you for reaching out! Yes, of course, you can take this issue! It's a good one to start with. It should take a smaller change to the PUBLIC_PATHS
in server/middleware/auth.ts
, and let's move the "rating" link to the header so it's accessible from any page. Let me know if you have any questions!
I began by moving the rating link from GameScreen.vue
to GlobalHeader.vue
. This required locating the necessary components, removing the link from the game screen, and placing it in the global header to ensure it was displayed for both authenticated and unauthenticated users.
After confirming the link was accessible from the header, I modified the public paths array inside the authentication middleware to include the /rating
path. The middleware checks if a requested path is public and redirects unauthenticated users to the login page if it isn’t.
I assumed this would resolve the issue, but I was met with a rendering error when I clicked the rating link while not logged in. The error indicated that .toFixed(2)
was being called on null
values in RatingTable.vue
. I suspected this was because the api/rating
fetch wasn’t returning any data in the development environment. To test this, I updated the component to conditionally render the values with .toFixed(2)
only if rating data was available.
This allowed me to access the rating page without logging in, but something was still wrong, as the table displayed blank entries.
To investigate the issue, I checked rating.get.ts
to confirm that fetching rating data didn’t rely on user-specific information. Then, I added console.log()
statements to the middleware logic in /server/middleware/auth.ts
to understand how redirects were handled. I also logged the status of the api/rating
fetch on the rating page.
I noticed that, for some reason, there was a redirect to /API/auth/users.
Then, I thought: "It must be it! Somewhere in the code, there is an attempt to fetch user details while not being authenticated, resulting in the redirect to the /login
being sent back to the rating page instead of the rating info". My assumption was partially confirmed by displaying the result of the /api/rating fetch
.
The logs revealed a redirect to /api/auth/user
. I thought that it was IT, that I found the underlying issue, as this suggested that an fetching user details was causing a redirect to the login page, which was being returned to the rating page instead of the actual ratings data. I confirmed an attempted redirect to /login
by inspecting the result of the /api/rating
fetch.
I discovered that the global header was making a fetch request to /api/auth/user
, and I suspected this was causing the issue. To test this, I temporarily removed the logic that fetched user data from the header. However, the problem persisted, and the rating page was still rendered incorrectly.
At this point, I decided to revisit my changes and carefully re-examine the logs. It became clear that I also needed to include /api/rating
in the public paths array to allow it to be accessed without authentication.
After adding /api/rating
to the public paths, everything worked as expected! It surprised me that such a small oversight caused so much debugging, but I was glad to have resolved the issue and was ready to submit my PR.
feat(rating): make rating page publicly accessible #90
https://github.com/user-attachments/assets/41f5cb0d-b87a-4852-bbc3-a905fdebe2bb
Made the rating page accessible to unauthenticated users by moving the link from game screen to the global header, and adding /rating
and /api/rating
to the list of public paths.
- [x] my PR is focused and contains one wholistic change
- [x] I have added screenshots or screen recordings to show the changes
I opened a draft PR and asked the maintainer for feedback on the link’s placement in the global header. I also suggested conditionally rendering the link so it wouldn’t appear on the rating page itself. However, the maintainer was satisfied with the changes and merged my PR.
Issue 2 - Make the Current Game publicly available
After my 1-st Pull Request, I quickly picked up a high priority
issue.
feat: add ability to view the current game without having to log in #43
Display the current game at the login screen.
E.g. we should update the login screen to look exactly like the screen after login, but instead of the code editor, we should display the login form.
Implementing the base requirements for this issue was relatively straightforward, as I was already familiar with the project and its routing setup. The maintainer requested that the login screen be updated to be exactly like the main game screen, with the only difference being that the left panel should instead display the login form.
To achieve this, I added /api/state
route used to fetch game state information for the game screen to the public paths in the middleware. I then reused the layout template and separator logic from the index.vue
(main game page) to build the login screen.
Code Reference: index.vue
<template>
<div class="flex flex-col h-screen w-screen">
<GlobalHeader />
<div class="flex flex-grow p-1 h-[90%]">
<div
ref="resizeEditorElement"
class="flex min-w-[300px] h-full w-1/2 mr-2"
>
<CodeEditor />
</div>
<div
ref="separator"
class="w-1 bg-gray-300 cursor-ew-resize"
/>
<div
ref="resizeGameElement"
class="flex min-w-[300px] w-1/2 overflow-auto"
>
<GameScreen />
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted, onUnmounted } from "vue";
export default defineComponent({
setup() {
const isResizing = ref(false);
const initialX = ref(0);
const initialWidthEditor = ref(0);
const initialWidthGame = ref(0);
const resizeEditorElement = ref<HTMLDivElement | null>(null);
const resizeGameElement = ref<HTMLDivElement | null>(null);
const separator = ref<HTMLDivElement | null>(null);
const startResize = (e: MouseEvent) => {
if (!resizeEditorElement.value) {
throw new Error("unexpected: no resizeEditorElement");
}
if (!resizeGameElement.value) {
throw new Error("unexpected: no resizeGameElement");
}
isResizing.value = true;
initialX.value = e.clientX;
initialWidthEditor.value = resizeEditorElement.value.offsetWidth;
initialWidthGame.value = resizeGameElement.value.offsetWidth;
};
const handleResize = (e: MouseEvent) => {
if (!resizeEditorElement.value) {
throw new Error("unexpected: no resizeEditorElement");
}
if (!resizeGameElement.value) {
throw new Error("unexpected: no resizeGameElement");
}
if (isResizing.value) {
const dx = e.clientX - initialX.value;
const newWidthEditor = initialWidthEditor.value + dx;
const newWidthGame = initialWidthGame.value - dx;
resizeEditorElement.value.style.width = `${newWidthEditor}px`;
resizeGameElement.value.style.width = `${newWidthGame}px`;
}
};
const stopResize = () => {
isResizing.value = false;
};
onMounted(() => {
if (!separator.value) {
throw new Error("unexpected: no separator");
}
separator.value.addEventListener("mousedown", startResize);
document.addEventListener("mousemove", handleResize);
document.addEventListener("mouseup", stopResize);
});
onUnmounted(() => {
if (!separator.value) {
throw new Error("unexpected: no separator");
}
separator.value.removeEventListener("mousedown", startResize);
document.removeEventListener("mousemove", handleResize);
document.removeEventListener("mouseup", stopResize);
});
return {
resizeEditorElement,
resizeGameElement,
separator,
};
},
});
</script>
My only issue with the code was that I initially moved the login form into a separate component, LoginForm.vue
, for better modularity. However, I discovered that this change caused issues with existing e2e (end-to-end) tests. The tests appeared to break because the final DOM structure rendered differently when the login form was imported as a separate component. (But I noticed a cool thing: because of Nuxt
, there was no need for explicit component export/import!).
I moved the login form back to the login page to resolve the issue, ensuring the DOM structure remained consistent. Once I confirmed that the login form worked as expected and the game screen displayed correctly on the login page, I submitted a draft pull request.
I also asked the maintainer for feedback on whether the login form should remain part of the page or be modularized into a separate component. Additionally, I mentioned how trying to move the form to a separate component tempered with the existing e2e tests.
feat(login): view current game without login #92
https://github.com/user-attachments/assets/7f2fea98-3c8b-4cc6-a313-8e19c729f4ab
- Added
/api/state
to public paths to make current game publicly accessible. - Updated the login screen to display the login form on the left and the current game on the right (reused the code from
index.vue
).
The game screen takes a couple of seconds to load.
- [x] my PR is focused and contains one wholistic change
- [x] I have added screenshots or screen recordings to show the changes
Comment for #92
Hello @yurijmikhalevich!
I have refactored the login page!
I have a doubt about the login form. Would it be better to move into a sperate component, or is it okay just to leave it as is? (I tried to move the form into a separate component, but this seems to have disrupted how the E2E tests interact with the page elements)
Also, would you suggest displaying any kind of placeholder/animation while the game screen is loading?
At the time of writing this post, I am awaiting feedback from the maintainer. As soon as I receive their suggestions, I plan to go ahead and implement any necessary changes.
Afterthoughts...
I’ve already learned quite a lot about Vue.js
and Nuxt
and feel satisfied with my progress. + One thing I’ve noticed is how much easier it has become, especially toward the end of the term, to navigate the structure of unfamiliar projects, even those built with frameworks I’ve never worked with before!
Top comments (0)