Why does my error handler only work for the first error, but fails on subsequent requests?

I’m trying to handle errors globally in Node/Express PostgreSQL/pg with an error handler function, and when I send a Postman request to the API for creating users to try to create a user with an email that already exists in the database, it first gives a proper custom response – as the duplicateError function is called due to the error having the 23505 code – and it creates a new custom operational error and sends it as the response. But if I then try to do it again, a TypeError happens instead of the previous PG error (duplicate key value violates unique constraint) so the response sent is instead the generic response for non operational errors

TypeError: Class constructor CustomError cannot be invoked without 'new'
    at C:Users\Desktopproject1backendnode_modulespg-poolindex.js:45:11
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async createUserService (file:///C:/Users//Desktop/project1/backend/src/models/userModel.js:15:18)
    at async createUser (file:///C:/Users//Desktop/project1/backend/src/controllers/userController.js:21:19) {
  status: 'fail',
  statusCode: 500
}

This is the db query function:

export const createUserService = async (name, email) => {
  const result = await pool.query(
    'INSERT INTO users(name,email) VALUES ($1, $2) RETURNING *',
    [name, email],
  );
  return result.rows[0];
};

This is the global error handling:

const prodErrors = (res, error) => {
  if (error.isOperational) {
    res.status(error.statusCode).json({
      status: error.status,
      message: error.message,
    });
  } else {
    res.status(500).json({
      status: 'error',
      message: 'Something went wrong, try again later',
    });
  }
};

const duplicateError = (error) => {
  const msg = 'Email must be unique';
  return new CustomError(msg, 400);
};

export const errorHandler = (error, req, res, next) => {
  error.status = error.status || 'fail';
  error.statusCode = error.statusCode || 500;
  console.log(error);

  if (process.env.NODE_ENV === 'development') {
    devErrors(res, error);
  } else if (process.env.NODE_ENV === 'production') {
    if (error.code == 23505) {
      error = duplicateError(error);
    }
    prodErrors(res, error);
  }
};

The custom error class:

class CustomError extends Error {
    constructor(message, statusCode) {
        super(message)
        this.statusCode = statusCode;
        this.status = statusCode>=400 && statusCode < 500 ? 'fail' : 'error';

        this.isOperational = true;

        Error.captureStackTrace = (this, this.constructor)
    }

}
export default CustomError;

For other errors, for example invalid email, it works fine. But for the duplicate value error it only works on the first request, after which it gives the TypeError. I’ve been looking for an answer the whole day and have no idea why it’s not working. I’m using the latest version of Express

Also, if I try doing it directly in the db query function, the same thing happens. First error is caught and a new custom error is thrown, but the second error does not contain the code and is only a TypeError

export const createUserService = async (name, email) => {
  try {
    const result = await pool.query(
      'INSERT INTO users(name,email) VALUES ($1, $2) RETURNING *',
      [name, email],
    );
    return result.rows[0];
  } catch (error) {
    // Handle PostgreSQL unique constraint error (23505)
    if (error.code === '23505') {
      throw new CustomError('Email must be unique', 400); // Use 400 for a Bad Request error
    }
    // If it's some other error, throw it again to be handled by a global error handler
    throw error;
  }
};

One more thing, if I instead of sending the error to the middleware, just console.log the error in the try catch block above, it’s the same pg error on all requests. So the issue happens after the first error is sent somehow

Why does threshold on IntersectionObserver not work on my element?

IntersectionObserver does not work as intended on my element in React. As you can see in code I have threshold set to 0.5 but animations are fired as soon as they appear on the screen and I do not understand why. Can someone explain? (I’ve included CSS just in case that has something to do with it because I’m not sure.)

Demo video

import { buttonHoverAnimation, rotateAnimation } from "../../animations/animations";
import backgroundImage from "../../assets/images/pexels.jpg";
import smileIcon from "../../assets/icons/smile.svg";
import "./ExploreCard.css";
import { useEffect, useRef } from "react";
import { slideAnimation } from "../../animations/animations";
type ExploreCardProps = {
  reverse?: boolean;
};

function ExploreCard({ reverse }: ExploreCardProps) {

  const buttonHover = buttonHoverAnimation({}, {}, 30);
  const contentRef = useRef<HTMLDivElement>(null);
  const imageRef = useRef<HTMLDivElement>(null)
  // Handler Slide animation
  useEffect(() => {
    if (!contentRef.current) return;
    const animation = slideAnimation(
      contentRef.current,
      reverse ?
      [-1000, 0] : [1000, 0]
    );
    const observer = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting) {
        animation.play();
        observer.unobserve(entries[0].target);
      }
    }, { threshold: 0.5 });
    observer.observe(contentRef.current);
    return () => {
      observer.disconnect();
    };
  });
  // Handle rotate animation ,
  useEffect(() => {
    if (!imageRef.current) return;
    const animation = rotateAnimation(imageRef.current)
    const observer = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting) {
        animation.play();
        observer.unobserve(entries[0].target);
      }
    },
     { threshold: 0.5 }
    );
    observer.observe(imageRef.current);
    return () => {
      observer.disconnect();
    };
  }, []);


  return (
    <div className={`explore-card ${reverse ? "reverse" : ""}`}>
      <div className="explore-image-wrapper" ref={imageRef}>
        <img src={backgroundImage} alt="Background-image" />
      </div>
      <div className={`explore-card-popup ${reverse ? "reverse" : ""}`}>
        <h2>2X</h2>
        <p>Faster</p>
      </div>
      <div className={`explore-content-wrapper ${reverse ? "reverse" : ""}`}ref={contentRef}>
        <div className="explore-content-icon">
          <img src={smileIcon} alt=":)" />
          <p>QUICK</p>
        </div>
        <div className="explore-content-header">
          <h1 className="explore-content-header-bold">HOME</h1>
          <h1 className="explore-content-header-italic">Refresh</h1>
        </div>
        <div className="explore-content-description">
          Experience the ultimate expert support — Creating spaces where comfort
          and productivity thrive.
        </div>
        <div className="explore-content-button">
          <button
            onMouseOver={buttonHover.handleMouseIn}
            onMouseOut={buttonHover.handleMouseOut}
          >
            <span>EXPLORE</span>
            <span>EXPLORE</span>
          </button>
        </div>
      </div>
    </div>
  );
}

export default ExploreCard;
@font-face {
  font-family: intern-light;
  src: url('../../assets/fonts/static/Inter_18pt-Thin.ttf');
}

@font-face {
  font-family: intern-regular;
  src: url('../../assets/fonts/static/Inter_18pt-Light.ttf');
}

@font-face {
  font-family: intern-bold;
  src: url('../../assets/fonts/static/Inter_24pt-Black.ttf');
}

@font-face {
  font-family: intern-italic;
  src: url('../../assets/fonts/static/Inter_18pt-ThinItalic.ttf');
}

.explore-card {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  position: relative;
  gap: 2vw;
  margin-top: 15vh;
  overflow: hidden;
  height: 50vh;
}

.explore-content-wrapper {
  width: 35%;
  display: flex;
  padding: 7%;
  display: inline-block;
}

.explore-content-icon > p{
  font-family: intern-regular;
  margin-left: 10px;
  font-weight: 500;
  letter-spacing: 2px;
}

.explore-content-icon {
  display: flex;
}

.explore-content-icon > img {
  width: 30px;
}

.explore-image-wrapper {
  display: flex;
  height: 50vh;
  width: 25%;
  border-radius: 70px;
  overflow: hidden;
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 2;
}

.explore-image-wrapper > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.explore-card-popup {
  top: 10%;
  left: 30%;
  border-radius: 50px;
  position: absolute;
  height: 34%;
  width: 13%;
  background-color: rgba(255, 255, 255, 0.7);
  backdrop-filter: blur(10px);
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  -webkit-box-shadow: 0px 10px 50px -10px rgba(0, 0, 0, 0.75);
  -moz-box-shadow: 0px 10px 50px -10px rgba(0, 0, 0, 0.75);
  box-shadow: 0px 10px 50px -10px rgba(0, 0, 0, 0.75);
  z-index: 3;
}

.explore-card-popup > h2 {
  font-family: intern-regular;
  font-weight: 100;
  font-size: 30px;
  margin: 0;
}

.explore-card-popup > p {
  font-family: intern-light;
  font-size: 20px;
  font-weight: 600;
  margin-top: 0;
  letter-spacing: 2px;
}

.explore-content-header {
  display: flex;
  font-size: 60px;
}

.explore-content-header > h1 {
  margin-top: 5px;
  margin-right: 10%;
  margin-bottom: 5px;
}

.explore-content-header-bold {
  font-family: intern-regular;
  font-weight: 300;
}

.explore-content-header-italic {
  font-family: intern-italic;
  font-weight: 100;
}

.explore-content-description {
  font-family: intern-regular;
  font-size: 18px;
  letter-spacing: 2px;
  line-height: 30px;
  margin-bottom: 30px;
}

.explore-content-button > button {
  font-family: intern-regular;
  height: 1.3rem;
  font-size: 1.3rem;
  background: none;
  border: 0;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.explore-content-button > button > span {
  position: relative;
  transition: transform 0.7s, opacity 0.7s;
  margin: 0;
}

.explore-card.reverse {
  flex-direction: row-reverse;
}
.explore-card-popup.reverse {
  left: 57%;
}

Jest Manual Mocking Not Functioning as Expected

I’m trying to mock mssql. I can see by debugging that the mock sql.connect function is being called but it doesn’t seem to be returning after being called and it’s causing the unit test to throw a timeout error and the test fails.

Here is the code that I’m testing:

            sql.connect(config.dbConnection, (err) => {
            if (err) {
                logger.info(err);
            } else {
                sql.query(`select * from XXX';`, (err, result) => {
                    if (err) {
                        logger.info("Error executing call: ", err);
                    } else {
                        resolve({ status: httpStatusCodes.StatusCodes.OK, apiResponse: result.recordsets[0][0].ZipCode });
                    }
                });
            }
        });

Here is the unit test:

it("should call sql.connect", async function () {
    const response = await regionalization.regionalizationAPI("02878");
    expect(response.status).toEqual(200);
});

Here is the manual mock file:

function connect() {
return jest.fn().mockReturnValue();

}

I have a breakpoint set in the connect function and the debugger stops in the function. No matter what mock function I use the execution isn’t returned back to the code and I keep seeing a timeout error.

Deactivate Cross Site Origin Protection [duplicate]

There are already several questions on this topic, but none have really been answered yet.
I really want to disable cross site origin security to access an iframe element and control it via javascript.

I realize that this is a security feature, but then you have to be able to disable it, especially if you explicitly want it in a certain context.
I know it’s possible because you can select the iframe via the hover cursor in the dev tools of any browser and then all javascript commands work directly in the iframe.
However, if I try this without using the cursor select function (directly with Javascript), I keep getting the error:

Command:

var iframe = document.querySelector('iframe');
iframe.contentWindow.contentDocument;

Error:
Uncaught SecurityError: Failed to read a named property 'contentDocument' from 'Window': Blocked a frame with origin “https://example.com” from accessing a cross-origin frame

How could I get around this?
Are there any browsers that allow me to bypass these functions?

Transfer Session between Javascript and PHP

Is there possible to convert Seesion data from Javascript to PHP and vice versa? Something like (I’m not sure about the input format in javascript.):

<script>
     session.setAttribute("username","John");
</script>

<?php
     echo $_SESSION["username"]; // it will write "John"
?>

How can I unit test Graphql Upload in api using Vitest

I wrote a test which creates a Venue with attachments property which is of Graphql Upload type. But when I try to run the test, it fails with error "message": "Variable "$attachments" got invalid value { fieldName: "1", filename: "valid-image.png", mimetype: "image/png", encoding: "7bit" } at "attachments[0]"; Upload value invalid.” basically Upload value invalid.

In backend the attachments is structure like that

{
  fieldName: '1',
  filename: 'Screenshot 2023-09-05 193500.png',
  mimetype: 'image/png',
  encoding: '7bit',
  createReadStream: [Function: createReadStream]
}

Unit test

it("should create a venue with valid attachments", async () => {
    const imagePath = join(__dirname, "test-files", "valid-image.png");
    const variables = {
      organizationId: orgId,
      name: `Test Venue ${faker.string.alphanumeric(5)}`,
      description: "Test venue with valid attachments",
      capacity: 50,
      attachments: [
        {
          fieldName: "1",
          filename: "valid-image.png",
          mimetype: "image/png",
          encoding: "7bit",
          createReadStream: () => createReadStream(imagePath),
        },
      ],
    };

    const result = await mercuriusClient.mutate(Mutation_createVenue, {
      headers: { authorization: `bearer ${authToken}` },
      variables,
    });

    console.log('result is ', result);
   
  })

I have never tested FileUpload so I’m not sure whether I’m correctly testing it or not.

Any help would be appreciated.

When exactly does navigator.clipboard.readText() prompt for user permission in Chrome?

Consider the following HTML file:

<div contenteditable="true" id="editable"></div>

<script>
  const editable = document.getElementById('editable');
  editable.addEventListener('paste', () => {
    navigator.clipboard.readText().then(x => console.log(x));
  });
</script>

Consider the following two scenarios in Chrome browser only:

  1. I press Ctrl+V (or Cmd+V on macOS) to paste text into the textbox. In this case, I do not get any permission prompt, and the console.log works.
    Note that this appears to be contradictory to the MDN documentation which states:

    Reading requires the Permissions API clipboard-read permission be granted. Transient activation is not required.

  2. If I run document.execCommand('paste', false) from the content script of a Chrome extension which has both the "clipboardRead" and the "clipboardWrite" permissions, then I do get the permission prompt. It is not clear to me why execCommand has a different behavior compared to the scenario above. The prompt is shown in this image:

    permissions prompt

My question: could anyone explain the behavior in line with relevant documentation, spec or Chromium source code?

text shape feature using Fabric.js

want to add support for curved text or text that follows a defined shape/path. The goal is to allow users to input text and dynamically adjust it to follow a curve (e.g., circular text, text along a wave) while maintaining standard text features like font size, color.

Laravel project organization [closed]

I’m trying to learn to program with laravel! I would like to understand how to best organize a project.

The project I would like to develop is the following:

Create a web app for a roulette betting strategy.

the strategy is based on the fibonacci sequence (0 1 1 2 3 5 7 12 etc.) so each bet is the sum of the previous two.

but I want to bet on a “column” or “row” of the roulette table only when it hasn’t come out for at least 5 consecutive times.

therefore I have to create a form in which I insert the last number that came out and save it in a sequence of numbers.

from the sequence check how many times each column and row hasn’t come out.

when one of these is not drawn for 5 consecutive times, highlight it as “to bet” and assign the bet 1 of the Fibonacci sequence eg € 1.00

if it is not drawn in the subsequent rounds, increase the bet following the Fibonacci sequence until that column or row is drawn.

at this point the sequence must be reset and you start again from the beginning counting from how many extractions the columns and rows have not come out.

it should not be too complicated but I do not understand how I should manage the models and functions to calculate the columns, rows and the bets following the numbers drawn.

I hope someone can give me some advice.

Thanks

Photo gallery Page [closed]

I am creating a website for a gaming server. One of the pages on the site is a photo gallery. I’ve created the gallery layout in a page called PhotoGallery.php. There are 3 options. The 1st is a radio input choosing either Photo or Video. The 2nd is a drop down menu that gets populated through a php function. This function locates the directory names in either /Images/Photo_Gallery/Pictures/ or /Video/Video_Gallery/Video/and echo back the directory names and option tags for the drop down. This menu is also hidden until the radio menu choice is made. This is done in a javascript function. This choice is then passed back to php to get the sub directories of the chosen path. The 3rd option another drop down menu that takes the value of the radio and the 1st drop down menu is populated with the directory names of the category folder if any. Then once all choices are made the gallery then displays all the pictures or videos Thumbnails found in that folder.

I have worked on this for 2 weeks now and can’t seem to get it working properly to populate the menus. I get the menu to show up but no option inside of it. Is there something wrong with my syntax or logic or is there a better cleaner approach?

PhotoGallery.php // code <!DOCTYPE html> <html lang="en-US"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Photo Gallery</title> <link rel="stylesheet" href="../../CSS/styles.css"> <link rel="stylesheet" href="../../CSS/photo_gallery.css"> <script src="../../Javascript/photo_gallery_functions.js"></script> </head> <header> <?php include "../../Website_Pages/Element_Pages/header.php" ?> </header> <body> <div class="photo_gallery_main_background_container"> <div class="left_nav_container"> <?php include "../../Website_Pages/Element_Pages/leftmenu.php" ?> </div> <div class="photo_gallery_main_container"> <h1>COMMUNE GALLERY</h1> <div class="background_container"> <div class="gallery_header" id="gallery_header"> <h2>"Collection"</h2> <div class="drop_down_menu_container" id="drop_down_menu_container"> <form action="../../PHP/photo_gallery_functions.php" method= "post"> <input type="radio" id="media_type_photo" name="media_type" value="Photo" checked="checked" onclick= showDropdown()> <label for="media_type_photo">Photo</label> <br> <input type="radio" id="media_type_video" name="media_type" value="Video" onclick= showDropdown()> <label for="media_type_video">Video</label> <br> <input type="submit" value="Submit"> </form> <br> <form action="../../PHP/photo_gallery_functions" method="get"> <select id="collection_menu" name="collection" style="display: none;"> <?php include "photo_gallery_functions.php"; media_type_choice ($media_type); ?> </select> </form> <br> <form action="../../PHP/photo_gallery_functions.php" method="get"> <select id="gallery_menu" name="gallery" style="display:none;"> <?php include "photo_gallery_functions.php"; collection_choice ($collection, media_type_gallery: $media_type_gallery) ?> </select> </form> <br> </div> <div class ="image_scrollbox_continer"> <div class="image_scrollbox" style="overflow: auto;"> <ul class = "image_gallery" id="image.gallery" style="display: none;"> <?php include "photo_gallery_functions.php"; thumbnailPreview ($media_type_gallery, $collection_gallery, $gallery_choice_gallery); ?> </ul> </div> </div> </div> </div> </div> </div> </body> <footer> <?php include "../../Website_Pages/Element_Pages/footer.php" ?> </footer> </html> // photo_gallery_funxtions.php <?php $media_type_gallery = "$media_type"; function media_type_choice ($media_type) { if ($media_type == "Photo") { $directoryPath= "/Images/Photo_Gallery/Pictures/"; $contents = scandir($directoryPath); $directories= array_filter ($contents, function ($item) use ($directoryPath) { return is_dir($directoryPath."/". $item) && $item !== "." && $item !=="..";}); foreach ($directories as $directory) { echo "<option id='photo_collection' name='photo_collection' value='$directory' onclick= showDropdownGallery()></option>"; } } else if ($media_type == "Video"); { $directoryPath= "/Video/Video_Gallery/Video"; $contents = scandir($directoryPath); $directories= array_filter ($contents, function ($item) use ($directoryPath) { return is_dir($directoryPath."/". $item) && $item !== "."&& $item !== "..";}); foreach ($directories as $directory) { echo "<option id='video_collection' name='video_collection' value='$directory' onclick= showDropdownGallery()></option>"; } } } function collection_choice ($collection, $media_type_gallery) { if ($media_type_gallery == "Photo") { $directoryPathGallery= "/Images/Photo_Gallery/$collection/"; } else if ($media_type_gallery == "Video"); { $directoryPathGallery= "/Video/Video_Gallery/$collection/"; } $contents = scandir($directoryPathGallery); $directoriesGallery= array_filter ($contents, function ($item) use ($directoryPathGallery) { return is_dir($directoryPathGallery."/". $item) && $item !== "."&& $item !== "..";}); foreach ($directoriesGallery as $directoryGallery) { echo "<option id='gallery_choice' name='gallery_choice' value='$directoryGallery' onclick=showThumbnails()>$directoryGallery</option>"; } } function thumbnailPreview ($media_type_gallery, $collection_gallery, $gallery_choice_gallery) { if ($media_type_gallery == "Photo") { $gallery_type = "/Images/Photo_Gallery/"; } else if ($media_type_gallery == "Video"); { $gallery_type= "/Video/Video_Gallery/"; } $directoryPathImage= "$gallery_type/Thumbnails/$collection_gallery/$gallery_choice_gallery/"; $contentsImage= scandir($directoryPathImage); $directoriesImage= array_filter ($contentsImage, function ($item) use ($directoryPathImage) { return is_file($directoryPathImage."/". $item) && $item !== "."&& $item !== "..";}); foreach ($directoriesImage as $directoryImage) { if ($media_type_gallery == "Photo") { $thumbnail = "<img src='/Images/Photo_Gallery/Thumbnails/$collection_gallery/$gallery_choice_gallery/$directoryImage.png' alt='$directoryImage' width='100px' height='100px'>"; echo "<a href='/Images/Photo_Gallery/Pictures/$collection_gallery/$gallery_choice_gallery/$directoryImage.png' target='_blank'>$thumbnail</a>"; } else if ($media_type_gallery == "Video"); { $videoThumbnail = "<img src='/Video/Video_Gallery/Thumbnails/$collection_gallery/$gallery_choice_gallery/$directoryImage.png' alt='$directoriesImage' width='100px' height='100px'>"; echo "<li id='thumbnail'><a href='/Video/Video_Gallery/Video/$collection_gallery/$gallery_choice_gallery/$directoriesImage.mp4 target='_blank'>$thumbnail</a></li>"; } } } ?>

Warning: mysqli_stmt_bind_param(): Argument must be passed by reference, value given [duplicate]

I use the following code to prepare a statement for msqli:

if($verfahren != ""){
    $where[] = "FIND_IN_SET(?,`verfahren`)>0";
    $types[] = "s";
    $params[] = $verfahren;
}
if($fachgebiet != ""){
    $where[] = "FIND_IN_SET(?,`fachgebiet`)>0";
    $types[] = "s";
    $params[] = $fachgebiet;
}
if($praxis != ""){
    $where[] = "`praxis` = ?";
    $types[] = "s";
    $params[] = $praxis;
}

$sql = "SELECT * FROM `my_table`";
if(isset($where)){
    $sql .= " WHERE ". implode(' AND ', $where);
    $stmt = mysqli_prepare($mysqli, $sql);
    # https://stackoverflow.com/a/43216404/25634160
    $refs = [$stmt, implode('', $types)];
    foreach($params as $param){
        $refs[] = $param;
    }
    unset($param);
    call_user_func_array('mysqli_stmt_bind_param', $refs);  <= Warning
} else {
    $stmt = mysqli_prepare($mysqli, $sql);
}

The fourth to last line throws three warnings:

Warning: mysqli_stmt_bind_param(): Argument #3 must be passed by reference, value given in /path/to/script.php on line 254

Warning: mysqli_stmt_bind_param(): Argument #4 must be passed by reference, value given in /path/to/script.php on line 254

Warning: mysqli_stmt_bind_param(): Argument #5 must be passed by reference, value given in /path/to/script.php on line 254

I understand from other Q&A here, such as this one, that I need to pass my variables as variables instead of values. What I don’t understand is where in my code I need to do that and how.

Could you please explain to me how I need to change my code?

How can be used TagsInput component to store related data using model hasmany relationship?

I have eloquent model to store specific car brand model years:

<?php

namespace AppModels;

use IlluminateDatabaseEloquentBuilder;
use IlluminateDatabaseEloquentModel;

class CarModel extends Model
{
    protected $fillable = ['brand_id', 'name'];

    public function brand()
    {
        return $this->belongsTo(CarBrand::class, 'brand_id');
    }

    public function years()
    {
        return $this->hasMany(CarModelYear::class, 'model_id');
    }

    public function classifications()
    {
        return $this->hasMany(CarClassification::class, 'model_id');
    }

    public function scopeWithCarStats(Builder $query)
    {
        $query->withCount([
            'classifications as totalClassifications',
        ]);

        $carClasses = CarClass::pluck('id');
        foreach ($carClasses as $carClassId) {
            $query->withCount([
                "classifications as car_class_{$carClassId}" => function (Builder $query) use ($carClassId) {
                    $query->where('class_id', $carClassId);
                }
            ]);
        }

        $carYears = CarModelYear::query()
            ->select('year')
            ->distinct()
            ->orderBy('year', 'asc')
            ->pluck('year');

        if($carYears->isNotEmpty()) {
            foreach ($carYears as $year) {
                $query->selectRaw("
                    (SELECT COUNT(*)
                     FROM car_classifications
                     JOIN car_model_years ON car_classifications.year_id = car_model_years.id
                     WHERE car_classifications.model_id = car_models.id
                     AND car_model_years.year = ?) as car_model_year_{$year}
                ", [$year]);
            }
        }
    }
}

class CarModelYear extends Model
{
    protected $fillable = ['model_id', 'year'];

    public function model()
    {
        return $this->belongsTo(CarModel::class, 'model_id');
    }

    public function classifications()
    {
        return $this->hasMany(CarClassification::class, 'year_id');
    }

    public function scopeWithCarStats(Builder $query)
    {
        $query->withCount([
            'classifications as totalClassifications',
        ]);

        $carClasses = CarClass::pluck('id');
        foreach ($carClasses as $carClassId) {
            $query->withCount([
                "classifications as car_class_{$carClassId}" => function (Builder $query) use ($carClassId) {
                    $query->where('class_id', $carClassId);
                }
            ]);
        }
    }
}

Here is my CarModel resource form:

public static function form(Form $form): Form
{
    return $form
        ->schema([
            FormsComponentsSection::make()
                ->columns()
                ->schema([
                    FormsComponentsTextInput::make('name')
                        ->label('Model Name')
                        ->required()
                        ->maxLength(255),
                    FormsComponentsSelect::make('brand_id')
                        ->label('Car Brand')
                        ->relationship('brand', 'name')
                        ->searchable()
                        ->preload()
                        ->required(),
                    FormsComponentsTagsInput::make('years')
                        ->label('Model Years')
                        ->placeholder('Enter model years, example: 2020, 2021, 2022')
                        ->required()
                        ->dehydrated(false)
                        ->unique()
                        ->splitKeys([',', ' '])
                        ->hint('Eneter model years with comma or space'),
                ])
        ]);
}

I tried debugging in CreateCarModel method named mutateFormDataBeforeCreate but not triggered anything:

protected function mutateFormDataBeforeCreate(array $data): array
{
    if(array_key_exists('years', $data)) {
        dd($data['years']);
    }

    return $data;
}

I get error when try create some model with specific years:

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'years' in 'where clause' (Connection: mysql, SQL: select count(*) as aggregate from `car_models` where `years` = 2024)

How I can correctly use TagsInput component to store car model years?

In API response some keys values are not coming as expected

    <?php

namespace AppServicesFeed;

use AppRepositoriesPollPollRepositoryInterface;
use AppRepositoriesPostPostRepositoryInterface;
use IlluminateHttpResourcesJsonAnonymousResourceCollection;
use IlluminatePaginationLengthAwarePaginator;
use IlluminateSupportFacadesLog;

class FeedService implements FeedServiceInterface
{
    public function __construct(
        private PostRepositoryInterface $postRepository,
        private PollRepositoryInterface $pollRepository
    ) {}

    /** Send all feeds
     *
     * @param array $data The filtered data
     * @return array The feed array
     */
    public function getFeeds(array $data): array
    {
        $page = $data['page'] ?? config('data.pagination.default_page', 1);
        $limit = $data['limit'] ?? config('data.pagination.default_limit', 10);

        // Get paginated posts and polls (fetching only required records)
        $posts = $this->postRepository->getPosts($data);
        $polls = $this->pollRepository->getPolls($data);

        // Ensure collections exist and default to empty collections if null
        $postsCollection = isset($posts->collection) ? collect($posts->collection) : collect();
        $pollsCollection = isset($polls->collection) ? collect($polls->collection) : collect();

        // Merge and sort by combined_created_at
        $mergedFeed = $postsCollection->merge($pollsCollection)
            ->sortByDesc('combined_created_at')
            ->values();

        // Paginate the merged feed
        $total = $mergedFeed->count();
        $paginatedItems = $mergedFeed->slice(($page - 1) * $limit, $limit)->values();

        // Apply transformations **only on the paginated items**
        $startIndex = ($page - 1) * $limit; // Start index based on pagination

        $transformedFeed = $paginatedItems->map(function ($item) use (&$startIndex, $data) {
            // Ensure JSON is properly decoded as an **object**
            $item->show_user_details = json_decode($item->show_user_details, false);

            $item->is_shared = (bool) $item->is_shared;

            // Generate unique ID using type, ID, index, page, and limit
            $uniqueKey = "{$item->type}-{$item->id}-{$startIndex}-{$data['page']}-{$data['limit']}";
            $item->unique_id = hash('sha256', $uniqueKey);

            $startIndex++; // Increment index

            return $item; // Ensure transformation is applied
        });

        // Create the paginator
        $paginatedFeed = new LengthAwarePaginator(
            $transformedFeed, // Only transformed paginated items
            $total, // Total items count
            $limit, // Items per page
            $page, // Current page
            ['path' => request()->url(), 'query' => request()->query()] // Maintain query parameters
        );

        return [
            'post' => $paginatedFeed->items(), // Extract items directly
            'pagination' => [
                'total' => $paginatedFeed->total(),
                'per_page' => $paginatedFeed->perPage(),
                'current_page' => $paginatedFeed->currentPage(),
                'last_page' => $paginatedFeed->lastPage(),
                'from' => $paginatedFeed->firstItem(),
                'to' => $paginatedFeed->lastItem(),
            ],
        ];
    }
}

    /**
     * Send all posts
     *
     * @param array $data The filtered data
     * @return AnonymousResourceCollection The post collection
     */
    public function getPosts(array $data): AnonymousResourceCollection
    {
        $this->setPaginationAndOrderBy($data);

        $postableId = $data["postable_id"] ?? null;
        $postableType = $data["postable_type"] ?? null;

        $username = $data['username'] ?? null;

        // Fetch user ID from username
        $userId = null;
        if ($username) {
            $userId = $this->user->where('username', $username)->value('id');
        }

        // Original Posts with Creator's Name
        $originalPosts = $this->post
            ->select(
                'posts.*',
                DB::raw('posts.created_at as combined_created_at'),
                DB::raw("
                CASE 
                    WHEN posts.postable_type LIKE '%User' THEN users.username
                    WHEN posts.postable_type LIKE '%Club' THEN clubs.name
                    ELSE NULL
                END as show_user
            "),
                DB::raw("
                CASE 
                    WHEN posts.postable_type LIKE '%User' THEN CAST(
                        JSON_OBJECT(
                            'id', users.id,
                            'profile_image', users.profile_image,
                            'username', users.username,
                            'email', users.email,
                            'first_name', users.first_name,
                            'last_name', users.last_name,
                            'gender',
                                CASE 
                                    WHEN users.gender = 0 THEN 'Male'
                                    WHEN users.gender = 1 THEN 'Female'
                                    WHEN users.gender = 2 THEN 'Others'
                                    ELSE NULL
                                END
                        ) AS JSON
                    )
                    WHEN posts.postable_type LIKE '%Club' THEN CAST(
                        JSON_OBJECT(
                            'id', clubs.id,
                            'name', clubs.name,
                            'description', clubs.description
                        ) AS JSON
                    )
                    ELSE NULL
                END as show_user_details
            "),
                DB::raw("FALSE as is_shared") // Adding is_shared flag
            )
            ->leftJoin('users', function ($join) {
                $join->on('posts.postable_id', '=', 'users.id')
                    ->where('posts.postable_type', 'App\Models\User');
            })
            ->leftJoin('clubs', function ($join) {
                $join->on('posts.postable_id', '=', 'clubs.id')
                    ->where('posts.postable_type', 'App\Models\Club');
            });

        // Apply filters for `postable_id` and `postable_type`
        if ($postableId && $postableType) {
            $originalPosts->where('posts.postable_id', $postableId)
                ->where('posts.postable_type', $postableType);
        }

        // Apply filter for user posts if username exists
        if ($userId) {
            $originalPosts->where('posts.postable_id', $userId)
                ->where('posts.postable_type', 'App\Models\User');
        }

        // Shared Posts with Sharer's Username
        $sharedPosts = $this->post
            ->select(
                'posts.*',
                DB::raw('post_shares.created_at as combined_created_at'),
                'shared_users.username as show_user',
                DB::raw("
                    CAST(
                        JSON_OBJECT(
                            'id', shared_users.id,
                            'profile_image', shared_users.profile_image,
                            'username', shared_users.username,
                            'email', shared_users.email,
                            'first_name', shared_users.first_name,
                            'last_name', shared_users.last_name,
                            'gender',
                                CASE 
                                    WHEN shared_users.gender = 0 THEN 'Male'
                                    WHEN shared_users.gender = 1 THEN 'Female'
                                    WHEN shared_users.gender = 2 THEN 'Others'
                                    ELSE NULL
                                END
                        ) AS JSON
                    ) as show_user_details
                "),
                DB::raw("TRUE as is_shared") // Adding is_shared flag
            )
            ->join('post_shares', 'posts.id', '=', 'post_shares.post_id')
            ->join('users as shared_users', 'post_shares.user_id', '=', 'shared_users.id');

        // Apply filter for shared posts if username exists
        if ($userId) {
            $sharedPosts->where('post_shares.user_id', $userId);
        }

        // Combine Original Posts + Shared Posts using UNION
        $posts = $originalPosts
            ->unionAll($sharedPosts)
            ->orderByDesc('combined_created_at')
            // ->paginate($this->limit, ['*'], 'page', $this->page);
            ->get();

        // $startIndex = ($data['page'] - 1) * $data['limit']; // Start index based on pagination

        // // Handles JSON decoding
        // $posts->transform(function ($post) use ($startIndex, $data) {
        //     $post->show_user_details = json_decode($post->show_user_details, true);
        //     $post->is_shared = (bool) $post->is_shared; // Ensure boolean type

        //     // Generate unique ID using post ID, index, page, and limit
        //     $uniqueKey = "{$post->type}-{$post->id}-{$startIndex}-{$data['page']}-{$data['limit']}";

        //     // Generate unique ID using hash
        //     $post->unique_id = hash('sha256', $uniqueKey);

        //     $startIndex++; // Increment the index for next post

        //     return $post;
        // });

        // Load default relations
        $posts->load([
            'postMedias',
            'postable',
            'comments.commentable',
            'club'
        ]);

        return PostResource::collection($posts);
    }

    /**
     * Send all polls
     *
     * @param array $data The filtered data
     * @return AnonymousResourceCollection The poll collection
     */
    public function getPolls(array $data): AnonymousResourceCollection
    {
        $this->setPaginationAndOrderBy($data);

        $pollableId = $data['pollable_id'] ?? null;
        $pollableType = $data['pollable_type'] ?? null;

        $username = $data['username'] ?? null;

        // Fetch user ID from username
        $userId = null;
        if ($username) {
            $userId = $this->user->where('username', $username)->value('id');
        }

        // Original Polls with Creator's Name
        $originalPolls = $this->poll
            ->select(
                'polls.*',
                DB::raw('polls.created_at as combined_created_at'),
                DB::raw("
                CASE 
                    WHEN polls.pollable_type LIKE '%User' THEN users.username
                    WHEN polls.pollable_type LIKE '%Admin' THEN admins.name
                    WHEN polls.pollable_type LIKE '%Club' THEN clubs.name
                    ELSE NULL
                END as show_user
            "),
                DB::raw("
                CASE 
                    WHEN polls.pollable_type LIKE '%User' THEN CAST(
                        JSON_OBJECT(
                            'id', users.id,
                            'profile_image', users.profile_image,
                            'username', users.username,
                            'email', users.email,
                            'first_name', users.first_name,
                            'last_name', users.last_name,
                            'gender',
                                CASE 
                                    WHEN users.gender = 0 THEN 'Male'
                                    WHEN users.gender = 1 THEN 'Female'
                                    WHEN users.gender = 2 THEN 'Others'
                                    ELSE NULL
                                END
                        ) AS JSON
                    )
                    WHEN polls.pollable_type LIKE '%Admin' THEN CAST(
                        JSON_OBJECT(
                            'id', admins.id,
                            'name', admins.name,
                            'email', admins.email
                        ) AS JSON
                    )
                    WHEN polls.pollable_type LIKE '%Club' THEN CAST(
                        JSON_OBJECT(
                            'id', clubs.id,
                            'name', clubs.name,
                            'description', clubs.description
                        ) AS JSON
                    )
                    ELSE NULL
                END as show_user_details
            "),
                DB::raw("FALSE as is_shared") // Adding is_shared flag
            )
            ->leftJoin('users', function ($join) {
                $join->on('polls.pollable_id', '=', 'users.id')
                    ->where('polls.pollable_type', 'App\Models\User');
            })
            ->leftJoin('admins', function ($join) {
                $join->on('polls.pollable_id', '=', 'admins.id')
                    ->where('polls.pollable_type', 'App\Models\Admin');
            })
            ->leftJoin('clubs', function ($join) {
                $join->on('polls.pollable_id', '=', 'clubs.id')
                    ->where('polls.pollable_type', 'App\Models\Club');
            });

        // Apply filters for `postable_id` and `postable_type`
        if ($pollableId && $pollableType) {
            $originalPolls->where('polls.pollable_id', $pollableId)
                ->where('polls.pollable_type', $pollableType);
        }

        // Apply filter for user polls if username exists
        if ($userId) {
            $originalPolls->where('polls.pollable_id', $userId)
                ->where('polls.pollable_type', 'App\Models\User');
        }

        // Shared Polls with Sharer's Username
        $sharedPolls = $this->poll
            ->select(
                'polls.*',
                DB::raw('poll_shares.created_at as combined_created_at'),
                'shared_users.username as show_user',
                DB::raw("
                CAST(
                    JSON_OBJECT(
                        'id', shared_users.id,
                        'profile_image', shared_users.profile_image,
                        'username', shared_users.username,
                        'email', shared_users.email,
                        'first_name', shared_users.first_name,
                        'last_name', shared_users.last_name,
                        'gender',
                            CASE 
                                WHEN shared_users.gender = 0 THEN 'Male'
                                WHEN shared_users.gender = 1 THEN 'Female'
                                WHEN shared_users.gender = 2 THEN 'Others'
                                ELSE NULL
                            END
                    ) AS JSON
                ) as show_user_details
                "),
                DB::raw("TRUE as is_shared") // Adding is_shared flag
            )
            ->join('poll_shares', 'polls.id', '=', 'poll_shares.poll_id')
            ->join('users as shared_users', 'poll_shares.user_id', '=', 'shared_users.id');

        // Apply filter for shared polls if username exists
        if ($userId) {
            $sharedPolls->where('poll_shares.user_id', $userId);
        }

        // Combine Original Polls + Shared Polls using UNION
        $polls = $originalPolls
            ->unionAll($sharedPolls)
            ->orderByDesc('combined_created_at')
            //->paginate($this->limit, ['*'], 'page', $this->page);
            ->get();

        // $startIndex = ($data['page'] - 1) * $data['limit']; // Start index based on pagination

        // // Handles JSON decoding
        // $polls->transform(function ($poll) use ($startIndex, $data) {
        //     $poll->show_user_details = json_decode($poll->show_user_details, true);
        //     $poll->is_shared = (bool) $poll->is_shared; // Ensure boolean type

        //     // Generate unique ID using post ID, index, page, and limit
        //     $uniqueKey = "{$poll->type}-{$poll->id}-{$startIndex}-{$data['page']}-{$data['limit']}";

        //     // Generate unique ID using hash
        //     $poll->unique_id = hash('sha256', $uniqueKey);

        //     $startIndex++; // Increment the index for next post

        //     return $poll;
        // });

        // Load default relations
        $polls->load([
            'pollOptions',
            'pollable',
            'comments.commentable',
            'club',
        ]);

        return PollResource::collection($polls);
    }

Here we’ve an API /feeds?page=1&limit=10 and API response will be sent from the getFeeds function and we’re merging the post and poll data and after merging we apply the pagination.
In response of the API show_user_details not decoded properly.Instead of is_shared 0 or 1 in response false or true should come. And unique_id is not present in the response