DEV Community

arinak1017
arinak1017

Posted on

The Open Source Finale: Pt. 2

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

GitHub logo 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

Aibyss social image

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
Enter fullscreen mode Exit fullscreen mode

Create new user

npm run create-user <username>
Enter fullscreen mode Exit fullscreen mode

Running the tests

unit tests

npm run test
Enter fullscreen mode Exit fullscreen mode

e2e tests

First, setup e2e tests by running npm run test:e2e:install, then run the tests with:

npm run test:e2e
Enter fullscreen mode Exit fullscreen mode

Contributors guide

We follow conventional commits, name your PRs accordingly

Production

Build the application for production:

npm run build
Enter fullscreen mode Exit fullscreen mode

Locally preview production build:

npm run preview
Enter fullscreen mode Exit fullscreen mode

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-stack Vue apps.

Just take a look at Nuxt DevTools!
Image description

  • 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.

Image description

This allowed me to access the rating page without logging in, but something was still wrong, as the table displayed blank entries.

Image description

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.

Image description

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.

Image description

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.

Image description

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.

Image description

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

How does this PR impact the user?

https://github.com/user-attachments/assets/41f5cb0d-b87a-4852-bbc3-a905fdebe2bb

Description

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.

Checklist

  • [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.

Image description

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.

Image description

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>
Enter fullscreen mode Exit fullscreen mode

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!).

Image description

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

How does this PR impact the user?

https://github.com/user-attachments/assets/7f2fea98-3c8b-4cc6-a313-8e19c729f4ab

Description

  • 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).

Limitations

The game screen takes a couple of seconds to load.

Checklist

  • [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

arilloid avatar
arilloid commented on

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)