Laravel SQL database user posts tags interection (post suggestions)

This is a Laravel 10 project and, I have a table posts – (title(varchar), category (varchar), tags (json), postImg, created_at , delete_at).
And tablet user_post_interactions where I am storing (user_id, post_id, introduction_type).
And I want to show new posts to user based on there post information like tags, category.
How I can do it without any memory overflow (only by using SQL)?

$interactedPostIds = UserPostInteractions::where('user_id', Auth::user()->id)
            ->pluck('post_id');
$page = FacadesRequest::get('page', 1);
$perPage = 10;
//Get hashtags from those posts
$interactedHashtags = Posts::whereIn('id', $interactedPostIds)
            ->pluck('tags')
            ->map(function ($tags) {
                return json_decode($tags); // Convert JSON string to array
            })
            ->flatten()->countBy()->sortDesc()->keys()->toArray();
 $userId = Auth::user()->id;
        $posts =  Posts::where('is_approved', 1)
            ->leftJoin('user_post_interactions', function ($join) use ($userId) {
                $join->on('posts.id', '=', 'user_post_interactions.post_id')
                    ->where('user_post_interactions.user_id', '=', $userId);
            })
            ->selectRaw('posts.*, GROUP_CONCAT(DISTINCT user_post_interactions.interaction_type) as interaction_types')
            ->groupBy('posts.id') // Ensure unique posts
            ->get();
 // Sort using tags match
        $sorted = $posts->sortByDesc(function ($post) use ($interactedHashtags) {
            $postTags = json_decode($post->tags, true) ?? [];
            return count(array_intersect($postTags, $interactedHashtags));
        });
        $posts = new LengthAwarePaginator(
            $sorted->forPage($page, $perPage),
            $sorted->count(),
            $perPage,
            $page,
            ['path' => FacadesRequest::url(), 'query' => FacadesRequest::query()]
        );

Currently, I just pluck post_id from user_post_interactions and by using it I pluck ‘tags’ from post table and after that match these tags for rank posts by tags and take 10 posts to show to user, but when there is more than 1k+ posts it’s going to crash (because of PHP memory limit), is there any other way (using SQL or something else) to do without going to crash?

How can I add additional fees/items from the checkout page in a WooCommerce store?

I have a WooCommerce store specialized in renting out beach equipment. I added a dropdown menu to the checkout page where a customer can select for the order to be delivered or that they can pick it up themselves. If they select the delivery option, a $10 fee is added to the cart total, listed as Delivery Fee. If they select pickup, a second dropdown appears where they can select a pickup location. I want to add the option for clients to purchase cold drinks from the checkout page if they select a specific pickup location (Kamini’s Kitchen), the list of which should pop up when they select this one pickup location. I wrote it so that checkboxes appear for each drink and a quantity that can be selected. When a drink is added, it should be added to the cart under its proper name and the listed price should be added to the total (times the multiplier). Lastly, the selected drink options should appear in the order e-mails and on the admin backend.

The problem is that when a drink is selected from the list, it doesn’t get added to the cart total. The cart does refresh, but nothing gets added. I have existing code that some of the folks over here helped me write, but the drink extensions part that I’m trying to do is not working for me. I made an array for all the drinks and the dollar amounts, and JS code to make the list appear conditionally. The part where they’re supposed to be added to the cart dynamically through AJAX is where I’m stuck.

Here is my code so far:

// Utility function: Get Delivery or Pickup options
function get_delivery_or_pickup_options() {
    return array(
        ''          => esc_html__( 'Select an option', 'woocommerce' ),
        'delivery'  => esc_html__( 'Delivery Service', 'woocommerce' ),
        'pickup'    => esc_html__( 'Pickup', 'woocommerce' )
    );
}

// Utility function: Get pickup location options
function get_pickup_location_options() {
    return array(
        ''                  => esc_html__( 'Select an option', 'woocommerce' ),
        'main_office'       => esc_html__( 'Pavia 20F - Main Office', 'woocommerce' ),
        'kaminis_kitchen'   => esc_html__( "Kamini's Kitchen (Baby Beach)", 'woocommerce' )
    );
}

add_action( 'woocommerce_before_order_notes', 'add_delivery_or_pickup_fields', 10 );
function add_delivery_or_pickup_fields( $checkout ) {
    // Existing dropdowns...
    woocommerce_form_field( 'delivery_or_pickup', array(
        'type'      => 'select',
        'class'     => array('form-row-wide'),
        'label'     => esc_html__( 'Delivery or Pickup?', 'woocommerce' ),
        'required'  => true,
        'options'   => get_delivery_or_pickup_options(),
    ), $checkout->get_value('delivery_or_pickup') );

    woocommerce_form_field('pickup_location', array(
        'type' => 'select',
        'class' => array('form-row-wide'),
        'label' => esc_html__( 'Select pickup location:', 'woocommerce' ),
        'required' => true,
        'options' => get_pickup_location_options(),
    ), $checkout->get_value('pickup_location'));

    // Drinks Section - Hidden by default, displayed by JS if pickup_location == kaminis_kitchen
    echo '<div id="drink_options_section" style="display:none;">';
    echo '<h3>' . esc_html__('If you’re renting a beach cooler, you have the option to pre-purchase drinks here and have the cooler loaded up with ice and drinks when picking it up', 'woocommerce') . '</h3>';

    $drink_items = array(
        'bag_of_ice'       => array('Bag of Ice', 4.00),
        'chill'            => array('Chill', 5.00),
        'balashi'          => array('Balashi', 5.00),
        'magic_mango'      => array('Magic Mango', 5.00),
        'amstel_bright'    => array('Amstel Bright', 6.00),
        'budweiser'        => array('Budweiser', 5.00),
        'bud_light'        => array('Bud Light', 5.00),
        'corona'           => array('Corona', 6.00),
        'heineken'         => array('Heineken', 5.00),
        'presidente'       => array('Presidente', 5.00),
        'modelo'           => array('Modelo', 5.00),
        'polar'            => array('Polar', 4.00),
        'guinness'         => array('Guinness', 7.00),
        'coke'             => array('Coke', 3.00),
        'coke_zero'        => array('Coke Zero', 3.00),
        'sprite'           => array('Sprite', 3.00),
        'sprite_zero'      => array('Sprite Zero', 3.00),
        'gingerale'        => array('Gingerale', 3.00),
        'fuze_tea'         => array('Fuze Tea', 3.00),
        'club_soda'        => array('Club Soda', 3.00),
        'tonic_water'      => array('Tonic Water', 3.00),
        'fanta_cherry'     => array('Fanta Cherry', 3.00),
        'fanta_grape'      => array('Fanta Grape', 3.00),
        'fanta_orange'     => array('Fanta Orange', 3.00),
        'bottled_water'    => array('Bottled Water', 2.57),
    );

    foreach ( $drink_items as $key => $item ) {
        echo '<p><label><input type="checkbox" name="drinks[' . esc_attr($key) . ']" value="1"> ' . esc_html($item[0]) . ' ($' . number_format($item[1], 2) . ')</label> ';
        echo '<input type="number" name="drink_qty[' . esc_attr($key) . ']" min="1" max="99" value="1" style="width:60px;margin-left:10px;" /></p>';
    }

    echo '</div>';
}


// JavaScript to toggle visibility of the pickup location dropdown and update delivery fee
add_action( 'woocommerce_checkout_init', 'add_delivery_or_pickup_js_script' );
function add_delivery_or_pickup_js_script() {
    wc_enqueue_js("

    const pickupField = $('#pickup_location_field');
    const drinkOptions = $('#drink_options_section');

    function toggleDrinkOptions() {
        if ($('#pickup_location').val() === 'kaminis_kitchen') {
            drinkOptions.show();
        } else {
            drinkOptions.hide();
        }
    }

    if ($('#delivery_or_pickup').val() !== 'pickup') {
        pickupField.hide();
        drinkOptions.hide();
    }

    $(document.body).on('change', '#delivery_or_pickup', function() { 
        const val = $(this).val();
        val === 'pickup' ? pickupField.show() : pickupField.hide();
        $.post(wc_checkout_params.ajax_url, {
            action: 'save_delivery_or_pickup_to_session',
            delivery_or_pickup: val
        }, function() {
            $(document.body).trigger('update_checkout');
        });
    });

    $(document.body).on('change', '#pickup_location', function() {
        toggleDrinkOptions();
        $(document.body).trigger('update_checkout');
    });

    // Send drink selections via AJAX
    function updateDrinkSessionData() {
        const selectedDrinks = {};
        $('#drink_options_section input[type=checkbox]:checked').each(function() {
            const key = $(this).attr('name').replace('drinks[', '').replace(']', '');
            const qty = $('#drink_qty_' + key).val() || 1;
            selectedDrinks[key] = qty;
        });

        $.post(wc_checkout_params.ajax_url, {
            action: 'save_drink_options_to_session',
            drinks: selectedDrinks
        }, function() {
            $(document.body).trigger('update_checkout');
        });
    }

    $(document.body).on('change', '#drink_options_section input', updateDrinkSessionData);

    toggleDrinkOptions();
");
}


// Ajax request: Save selection to WooCommerce session
add_action('wp_ajax_save_delivery_or_pickup_to_session', 'save_delivery_or_pickup_to_session');
add_action('wp_ajax_nopriv_save_delivery_or_pickup_to_session', 'save_delivery_or_pickup_to_session');
function save_delivery_or_pickup_to_session() {
    if ( isset($_POST['delivery_or_pickup']) ) {
        WC()->session->set('delivery_or_pickup', sanitize_text_field($_POST['delivery_or_pickup']));
    }
    wp_die();
}

// Add delivery fee if "Delivery Service" is selected
add_action( 'woocommerce_cart_calculate_fees', 'add_delivery_option_fee_based_on_session' );
function add_delivery_option_fee_based_on_session( $cart ) {
    if (is_admin() && !defined('DOING_AJAX')) return;

    if ( is_checkout() && WC()->session->get('delivery_or_pickup') === 'delivery' ) {
        $delivery_fee = 10;
        $cart->add_fee( esc_html__( 'Delivery Fee', 'woocommerce' ), $delivery_fee );
    }
}

// Save drink selection to WooCommerce session
add_filter('woocommerce_checkout_posted_data', 'capture_drink_data_during_ajax');
function capture_drink_data_during_ajax( $data ) {
    if ( isset($_POST['drinks']) && isset($_POST['drink_qty']) ) {
        $data['drinks'] = $_POST['drinks'];
        $data['drink_qty'] = $_POST['drink_qty'];
    }
    return $data;
}

add_action('wp_ajax_save_drink_options_to_session', 'save_drink_options_to_session');
add_action('wp_ajax_nopriv_save_drink_options_to_session', 'save_drink_options_to_session');
function save_drink_options_to_session() {
    $drinks = isset($_POST['drinks']) ? $_POST['drinks'] : array();
    $sanitized = array();

    foreach ($drinks as $drink => $qty) {
        $drink = sanitize_key($drink);
        $qty = max(1, intval($qty));
        $sanitized[$drink] = $qty;
    }
    
    WC()->session->set('drink_data', $sanitized);
    wp_die();
}


add_action('woocommerce_cart_calculate_fees', 'add_drink_fee_via_ajax', 20, 1);
function add_drink_fee_via_ajax($cart) {
    if (is_admin() && !defined('DOING_AJAX')) return;

    $pickup_location = isset($_POST['pickup_location']) ? sanitize_text_field($_POST['pickup_location']) : WC()->session->get('pickup_location');
    if ($pickup_location !== 'kaminis_kitchen') return;

    $drink_data = WC()->session->get('drink_data', []);
    if (empty($drink_data)) return;

    $prices = array(
        'bag_of_ice' => 4.00, 'chill' => 5.00, 'balashi' => 5.00, 'magic_mango' => 5.00,
        'amstel_bright' => 6.00, 'budweiser' => 5.00, 'bud_light' => 5.00, 'corona' => 6.00,
        'heineken' => 5.00, 'presidente' => 5.00, 'modelo' => 5.00, 'polar' => 4.00, 'guinness' => 7.00,
        'coke' => 3.00, 'coke_zero' => 3.00, 'sprite' => 3.00, 'sprite_zero' => 3.00,
        'gingerale' => 3.00, 'fuze_tea' => 3.00, 'club_soda' => 3.00, 'tonic_water' => 3.00,
        'fanta_cherry' => 3.00, 'fanta_grape' => 3.00, 'fanta_orange' => 3.00, 'bottled_water' => 2.57,
    );

    $total = 0;
    foreach ($drink_data as $drink => $qty) {
        if (isset($prices[$drink])) {
            $total += $prices[$drink] * $qty;
        }
    }

    if ($total > 0) {
        $cart->add_fee(__('Drink Options', 'woocommerce'), $total);
    }
}


add_action('woocommerce_checkout_order_processed', function () {
    WC()->session->__unset('drink_data');
}, 10, 1);

// Validate custom shipping options
add_action( 'woocommerce_after_checkout_validation', 'delivery_or_pickup_validation', 20, 2 );
function delivery_or_pickup_validation( $data, $errors ) {
    // Delivery or Pickup validation
    if ( isset($_POST['delivery_or_pickup']) && empty($_POST['delivery_or_pickup']) ) {
        $errors->add( 'delivery_or_pickup', esc_html__( 'You must choose between "Delivery" or "Pickup" option.', 'woocommerce' ), 'error' );
    }
    // Pickup location validation
    elseif ( isset($_POST['delivery_or_pickup']) && $_POST['delivery_or_pickup'] === 'pickup' 
    && isset($_POST['pickup_location']) && empty($_POST['pickup_location']) ) {
        $errors->add( 'pickup_location', esc_html__( 'You must choose a pickup location.', 'woocommerce' ), 'error' );
    }
}

// Save custom chosen shipping option details as order metadata
add_action( 'woocommerce_checkout_create_order', 'save_delivery_or_pickup_as_order_metadata', 10 );
function save_delivery_or_pickup_as_order_metadata( $order ) {
    if ( isset($_POST['delivery_or_pickup']) && !empty($_POST['delivery_or_pickup']) ) {
        $order->add_meta_data('_delivery_or_pickup', esc_attr($_POST['delivery_or_pickup']), true );
    }
    if ( isset($_POST['pickup_location']) && !empty($_POST['pickup_location']) ) {
        $order->add_meta_data('_pickup_location', esc_attr($_POST['pickup_location']), true);
    }
    // Remove the WC Session Variable
    if (  WC()->session->__isset('delivery_or_pickup') ) {
        WC()->session->__unset('delivery_or_pickup');
    }
}

add_action( 'woocommerce_checkout_create_order', 'save_drinks_to_order_meta', 20, 1 );
function save_drinks_to_order_meta( $order ) {
    if ( isset($_POST['drinks']) ) {
        $items = array(
            'bag_of_ice' => 'Bag of Ice', 'chill' => 'Chill', 'balashi' => 'Balashi', 'magic_mango' => 'Magic Mango',
            'amstel_bright' => 'Amstel Bright', 'budweiser' => 'Budweiser', 'bud_light' => 'Bud Light',
            'corona' => 'Corona', 'heineken' => 'Heineken', 'presidente' => 'Presidente',
            'modelo' => 'Modelo', 'polar' => 'Polar', 'guinness' => 'Guinness', 'coke' => 'Coke',
            'coke_zero' => 'Coke Zero', 'sprite' => 'Sprite', 'sprite_zero' => 'Sprite Zero',
            'gingerale' => 'Gingerale', 'fuze_tea' => 'Fuze Tea', 'club_soda' => 'Club Soda',
            'tonic_water' => 'Tonic Water', 'fanta_cherry' => 'Fanta Cherry', 'fanta_grape' => 'Fanta Grape',
            'fanta_orange' => 'Fanta Orange', 'bottled_water' => 'Bottled Water'
        );

        $drinks_ordered = array();
        foreach ($_POST['drinks'] as $key => $val) {
            $qty = intval($_POST['drink_qty'][$key] ?? 1);
            if ($qty > 0 && isset($items[$key])) {
                $drinks_ordered[] = $items[$key] . ' x' . $qty;
            }
        }

        if ( !empty($drinks_ordered) ) {
            $order->add_meta_data('_drink_options', implode(', ', $drinks_ordered));
        }
    }
}


// Update custom chosen shipping option details as USER metadata (useful for next checkout)
add_action( 'woocommerce_checkout_update_customer', 'save_delivery_or_pickup_as_user_metadata', 10 );
function save_delivery_or_pickup_as_user_metadata( $customer ) {
    if ( isset($_POST['delivery_or_pickup']) && !empty($_POST['delivery_or_pickup']) ) {
        $customer->update_meta_data('delivery_or_pickup', esc_attr($_POST['delivery_or_pickup']));
    }
    if ( isset($_POST['pickup_location']) && !empty($_POST['pickup_location']) ) {
        $customer->update_meta_data('pickup_location', esc_attr($_POST['pickup_location']));
    }
}

// Display chosen custom shipping option details in the admin panel under shipping address
add_action( 'woocommerce_admin_order_data_after_shipping_address', 'display_delivery_or_pickup_meta_in_admin_order', 10 );
function display_delivery_or_pickup_meta_in_admin_order( $order ) {
    // Delivery or Pickup
    if ( $value = $order->get_meta('_delivery_or_pickup') ) {
        $options = get_delivery_or_pickup_options();
        printf( '<p><strong>%s:</strong> %s', esc_html__('Shipping', 'woocommerce' ), $options[$value] );

        // Pickup location
        if ( $value = $order->get_meta('_pickup_location') ) {
            $options = get_pickup_location_options();
            printf( '<br><strong>%s:</strong> %s', esc_html__('Location', 'woocommerce' ), $options[$value] );
        }
        echo '</p>';
    }
}

// Display chosen custom shipping option details in order total lines (customer orders and email notifications)
add_filter( 'woocommerce_get_order_item_totals', 'insert_custom_line_order_item_totals', 10, 3 );
function insert_custom_line_order_item_totals( $total_rows, $order, $tax_display ){
    $shipping = $order->get_meta('_delivery_or_pickup');
    $options1  = get_delivery_or_pickup_options();
    $location = $order->get_meta('_pickup_location');
    $options2  = get_pickup_location_options();
    $key_target = array_key_exists('discount', $total_rows) ? 'discount' : 'cart_subtotal';
    $new_total_rows = array();

    // Loop through total rows
    foreach( $total_rows as $key => $value ){
        $new_total_rows[$key] = $total_rows[$key];

        if( 'cart_subtotal' === $key ) {
            $new_total_rows['shipping2'] = array(
                'label' => esc_html__('Shipping option:'),
                'value' => $options1[$shipping],
            );

            if ( $location ) {
                $new_total_rows['location'] = array(
                    'label' => esc_html__('Pickup location:'),
                    'value' => $options2[$location],
                );
                $new_total_rows['drink_options'] = array(
    'label' => esc_html__('Drink Options:'),
    'value' => esc_html($order->get_meta('_drink_options')),
);
            }

        }
    }
    return $new_total_rows;
}

Problem with wordpress custom post type url structure

I generate a tour post type with tour-type taxonomy:

<?php

add_action('init', 'site_register_taxonomy');
function site_register_taxonomy()
{
        register_taxonomy('tour-type', ['tour'], array(
        'labels' => array(
            'name'              => 'region',
            'all_items'         => 'all regions',
            'edit_item'         => 'edit region',
            'update_item'       => 'update region',
            'add_new_item'      => 'add new region',
        ),
        'public' => true,
        'hierarchical' => true,
        'rewrite' => array(
            'slug' => 'tour',
            'with_front' => false,
            'hierarchical' => true,
        ),
        'show_admin_column' => true,
        'show_in_rest' => false,
    ));
    }

add_filter('post_type_link', 'tour_custom_permalink', 10, 2);
function tour_custom_permalink($post_link, $post) {
    if ($post->post_type !== 'tour') return $post_link;

    $terms = get_the_terms($post->ID, 'tour-type');
    if (!empty($terms) && !is_wp_error($terms)) {
        return str_replace('%tour-type%', $terms[0]->slug, $post_link);
    } else {
        return str_replace('%tour-type%', 'uncategorized', $post_link);
    }
}

add_action('init', 'create_post_type');
function create_post_type()
{
        register_post_type('tour', array(
        'label' => 'تور',
        'public' => true,
        'rewrite' => array(
            'slug' => 'tour/%tour-type%',
            'with_front' => false,
            'hierarchical' => true
        ),
        'has_archive' => 'tour',
        'supports' => array('title', 'editor', 'thumbnail'),
        'taxonomies' => array('tour-type'),
        'show_ui' => true,
        'show_in_menu' => true,
        'show_in_admin_bar' => true,
        'hierarchical' => true,
        'menu_icon' => 'dashicons-admin-site-alt',
        'labels' => array(
            'name' => 'tour',
            'singular_name' => 'tour',
            'menu_name' => 'tour',
            'add_new_item' => 'add tour',
            'edit_item' => 'edit tour',
            'new_item' => 'new tour',
            'view_item' => 'show',
            'search_items' => 'search tour',
            'not_found' => 'not found',
            'not_found_in_trash' => 'not found in trash',
        )
    ));
}

now the result of hierarchical taxonomy is like this:

site.com/tour/turkey/istanbul/istanbul-from-dubai/

for single post is like this:

site.com/tour/turkey/istanbul/istanbul-from-dubai/istanbul-from-dubai-flight-5days

the problem is that I get 404 error for single posts.

another question is how can I shorten my url when I have two similar slug.

for example: I create from-dubai for istanbul parent and another for izmir. when I create number two slug it’s changed to this: from-dubai-izmir. I think that is the behaviour of wordpress for slugs.This behavior is the same when creating single posts. how to change it?

If you have a solution for these two problems, it would be appreciated.

How to correctly configure CORS in Laravel 12.13 with the HandleCors middleware in bootstrap/app.php?

I am currently working with Laravel 12.13 and facing an issue with CORS. I have configured the HandleCors middleware and the CORS settings in my application, but I am still getting CORS errors when trying to make requests from the frontend to the backend.

Here’s what I have done so far:

In bootstrap/app.php, I have added the following middleware configuration:

<?php use IlluminateFoundationApplication; 
use IlluminateFoundationConfigurationExceptions;
use IlluminateFoundationConfigurationMiddleware; 
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
    web: __DIR__.'/../routes/web.php',
    api: __DIR__.'/../routes/api.php',
    commands: __DIR__.'/../routes/console.php',
    health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
    // Register native CORS middleware in Laravel 12
    $middleware->append(IlluminateHttpMiddlewareHandleCors::class);

    // You can add other global middleware here
})
->withExceptions(function (Exceptions $exceptions) {
    //
})
->create();

I have created the cors.php configuration file inside the config directory with the following content:

<?php
return [
'paths' => ['api/v1/*'],
// Methods I want to allow for CORS:
'allowed_methods' => ['POST', 'GET', 'OPTIONS', 'PUT', 'DELETE'],
// Origins from where I allow access without CORS interference:
'allowed_origins' => ['https://annaponsprojects.com'],

'allowed_origins_patterns' => [],

// Headers I want to allow to receive in frontend requests:
'allowed_headers' => ['Content-Type', 'Authorization', 'Accept'],

'exposed_headers' => [],
// Don't perform preflight until 1 hour has passed:
'max_age' => 3600,
// Indicates if the browser can send cookies (works with tokens):
'supports_credentials' => false ];

However, I am still encountering CORS issues when sending a request from the frontend to my backend API. The error message I receive is:

Access to fetch at ” from origin ” has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

What have I tried so far?
I’ve made sure to include the correct middleware (HandleCors) in the bootstrap/app.php.

I’ve configured CORS settings in the config/cors.php file.

I’ve cleared the cache (php artisan config:clear and php artisan cache:clear).

What could I be missing?
I suspect the issue might be related to the CORS configuration, but I am not sure. Is there anything else I should check or configure in Laravel 12.13 to resolve this issue?

FirebaseError: Firebase Storage: User does not have permission to access ‘vehicles/LwDaUZiAn8WIfye5Oj6S/backgroundPic’. (storage/unauthorized)

FirebaseError: Firebase Storage: User does not have permission to access ‘vehicles/LwDaUZiAn8WIfye5Oj6S/backgroundPic’. (storage/unauthorized)

I am completly stuck with firebase rules. It’s not working at all. I already checked if userid, carid is correct and it was correct. Firestore path is also correct.

const fetchCar = async (user: User, carId: string) => {
    setLoading(true);
    setError(null);
    try {
      const carRef = doc(db, "vehicles", carId);
      const carSnap = await getDoc(carRef);
      if (carSnap.exists()) {
        let carData: any = { id: carSnap.id, ...carSnap.data() };
        // Try to fetch backgroundPic from storage
        try {
          const url = await getDownloadURL(
            storageRef(storage, `vehicles/${carId}/backgroundPic`)
          );
          carData.image = url;
        } catch (e) {
          carData.image = "/logo.png";
        }
        setCar(carData);
        document.title = `IDMOTO | ${carData.manufacturer} ${carData.model}`;
      } else {
        setError("Car not found.");
      }
    } catch (err) {
      console.error("Error fetching car data:", err);
      setError("Failed to fetch car data.");
    }
    setLoading(false);
  };

  const handleUpload = async (file: File) => {
    if (!user) {
      alert("You must be logged in!");
      return;
    }
    if (!carId) {
      alert("No car ID!");
      return;
    }
    await uploadPhoto(file, user, carId);
    await fetchCar(user, carId);
    // do something with the url
  };
export const uploadPhoto = async (file: File, user: User, carid: string): Promise<string> => {
    if (!file) throw new Error("No file provided.");
    if (!user) throw new Error("User is not authenticated. Please log in.");
    if (!carid) throw new Error("Car ID is not provided.");

    const storageRef = ref(storage, `vehicles/${carid}/backgroundPic`);
    const uploadTask = uploadBytesResumable(storageRef, file);

    return new Promise<string>((resolve, reject) => {
        uploadTask.on(
            "state_changed",
            (snapshot: UploadTaskSnapshot) => {
                const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
                console.log(`Upload is ${progress.toFixed(0)}% done`);
            },
            (error) => {
                console.error("Upload failed", error);
                reject(error);
            },
            async () => {
                try {
                    const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
                    resolve(downloadURL);
                } catch (error) {
                    reject(error);
                }
            }
        );
    });
};

Firestore rules:

rules_version = '2';

// Allow read/write access to a document keyed by the user's UID
service cloud.firestore {
  match /databases/{database}/documents {
    // User private data
    match /users/{userId} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
    match /users/{userId}/vehicles/{vehicleId} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }

    // Public vehicles collection
    match /vehicles/{vehicleId} {
        allow read: if true;
    allow get: if request.auth != null && request.auth.uid == resource.data.userID;
    allow write: if request.auth != null
    && (
      // Creating or updating: userID in the data being written
      request.auth.uid == request.resource.data.userID
      // Updating or deleting: userID in the existing document
      || (resource.data.userID != null && request.auth.uid == resource.data.userID)
    );
}}}

Firestorage rules:

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /vehicles/{carId}/{documents=**} {
      allow read: if true;
      allow write: if request.auth != null &&
        get(/databases/(default)/documents/vehicles/$(carId)).data.userID == request.auth.uid;
    }
  }
}

When firestorage rules only include “allow write: if true” or “allow write: if request.auth != null” it is working. Only after typing get line, it stops working.

Tried a lot of things. Nothing works. Is there any way to make it work like that?

Should I use Next.js for external links, or just use ?

I’m working on a Next.js project and wondering about the best practice for linking to external websites.

Next.js provides the component for internal routing. But when it comes to external links (e.g., to https://example.com), I’m not sure whether I should still put them in , or just use a plain tag.

For example:

import Link from 'next/link';

// Option 1 - Using <Link>
<Link href="https://example.com" target="_blank" rel="noopener noreferrer">
  Visit Example
</Link>

// Option 2 - Just <a>
<a href="https://example.com" target="_blank" rel="noopener noreferrer">
  Visit Example
</a>

Questions:

  • Is there any advantage or downside to using for external URLs?
  • Does Next.js handle external links differently if wrapped in ?

Would appreciate any guidance or official docs if available.

jqxGrid: Nested grid not showing for some rows in Firefox (works fine in Chrome/Edge)

I’m using jqxGrid with nested grids (row details) enabled via rowdetails: true and initrowdetails to show detailed subgrids for each parent row. It works perfectly in Chrome and Edge, but in Firefox, some of the nested grids do not appear at all.

$("#orderComparisonGrid").jqxGrid({
            width: '100%',
            pageable: true,
            autoheight: true,
            rowdetails: true,
            initrowdetails: initRowDetailsFunction,
            // ...
});
        
function initRowDetailsFunction(index, parentElement, gridElement, record) {
            var detailsContainer = $($(parentElement).children()[0]);
            var nestedDiv = $("<div style='margin: 10px;'></div>");
            detailsContainer.append(nestedDiv);
        
            if (record.ComponentCodeList && record.ComponentCodeList.length > 0) {
                nestedDiv.jqxGrid({
                    width: '95%',
                    autoheight: true,
                    source: new $.jqx.dataAdapter({ localdata: record.ComponentCodeList, datatype: "array" }),
                    columns: [
                        { text: 'Component Name', datafield: 'Name' },
                        { text: 'Amount', datafield: 'Amount' }
                    ]
                });
       }
}

In Firefox, for some of the rows (first 9 rows) does not render
No console errors.
Wrapping the nested grid creation inside a setTimeout didn’t help
Any workaround or fix would be greatly appreciated.

Luggage Storage in Paddington Station London

Enjoy Stress-free travel with our secure luggage storage near Paddington Station. Whether you’re a tourist, business traveler, or local, free yourself from heavy bags and explore with ease. Our safe and convenient storage solution lets you travel light and worry-free. With affordable rates starting at just £3.99 per day, you can store your luggage for a few hours or even the whole day without breaking the bank.

Our location near Paddington Station makes it easy to drop off your bags and continue exploring the city without any extra weight. Your belongings are safe with us – we offer 24/7 security and easy access to ensure peace of mind. No more dragging heavy suitcases through crowded streets or worrying about the safety of your items. Whether you’re visiting for business or leisure, our luggage storage service gives you the freedom to make the most of your time in London.

India map rendering issue on d3 + svelte

I’m an absolute beginner when it comes to d3 + svelte and I’m trying to render a map of Indian districts in d3 in svelte framework. The json I’m accessing is from here. I’m trying to render the districts in geoMercator projection. I’m guessing the problem lies with the projection function

$: projection = geoMercator().fitSize([width, height], geojson);
    $: pathGenerator = geoPath(projection);
  
    let districts = [];
    $: if (geojson) districts = geojson.features.map(feature => {
      return {
        ...feature,
        path: pathGenerator(feature)
      };
    });

the rendering is done here

{#each districts as district}
        <!-- svelte-ignore a11y-no-static-element-interactions -->
        <path
          d={district.path}
        />
{/each}

But all I see on the browser is black box, which on inspection is the svg container under which I’m rendering the {#each} block. I only want to render the map of India before I move on to joining data and interactivity.

How to Handle Dynamic Geolayer and Geozone Names in Chart.js for Excel Report Download

I’m working on a system where users generate reports based on a selected time range, and the report includes charts created with Chart.js. The charts display data for “Geolayer” and “Geozone” entities, but their names are dynamic and change based on the selected time range (e.g., “MTB DEPARTURE LEVEL”, “CP COMBINED PLAN DOMESTIC”). This makes it challenging to implement a consistent Excel download feature for the chart data.

Example Image:

Here’s an example Excel sheet showing dynamic Geolayer names (e.g., MTB DEPARTURE LEVEL, CP COMBINED PLAN DOMESTIC) and their trolley counts. The names vary based on user-selected time ranges, complicating the Excel export process.

Excel Report Image

Questions:

  • How can I handle dynamic Geolayer and Geozone names in Chart.js to create a consistent and user-friendly Excel download feature?

JsSIP DTMF Issue with Spy/Whisper/Barge Feature

I’m attempting to implement FreePBX’s spy/whisper/barge functionality in a web application using JsSIP, but having issues with the DTMF functionality.

FreePBX Workflow

As per the FreePBX documentation:

FreePBX Feature code prefix allows spy/whisper/barge on the specified extension.

Usage:

  • Dial local extension with 556 prefix to spy
  • While spying on active channel use the following DTMF input to toggle modes:
    • DTMF 4 – spy mode
    • DTMF 5 – whisper mode
    • DTMF 6 – barge mode

Current Implementation

I’m currently using JsSIP to connect to our FreePBX server and trying to implement the whisper functionality:

init: async () => {
  if (ua && ua.isConnected()) return;

  JsSIP.debug.disable("JsSIP:*");  

  const session = await getSession();
  if (!session) throw new Error("No active session found. Please log in.");

  const sipExtension = session.user.sip_config.sip_extension;
  const sipSecret = session.user.sip_config.sip_secret;

  if (!sipExtension || !sipSecret)
    throw new Error("SIP credentials not found in session.");

  const socket = new JsSIP.WebSocketInterface("wss://domain/ws");
  const configuration = {
    sockets: [socket],
    uri: `sip:${sipExtension}@[domain]`,
    password: sipSecret,
    display_name: "Client",
  };

  ua = new JsSIP.UA(configuration);

  // Various event handlers...
  ua.on("registered", () => {
    status = "Connected to PBX";
    // Successfully registered
  });

  ua.on("newRTCSession", (data) => {
    // Session handling...
  });

  ua.start();
},

whisperCall: async (sipConfig) => {
  console.log("Whispering to:", sipConfig);

  if (!ua)
    throw new Error("SIP user agent is not initialized. Call init first.");

  if (currentSession)
    throw new Error(
      "Another call is in progress. End the current call first."
    );

  const targetUri = `sip:${sipConfig.sip_extension}@${SIP_DOMAIN}`;

  // Store the session from the call
  currentSession = ua.call(targetUri);

  // Add event listener for when the call is connected
  currentSession.on("confirmed", () => {
    // Only send DTMF after the call is established
    currentSession.sendDTMF(5, { transportType: "RFC2833" });
    console.log("DTMF tone sent");
  });

  if (!currentSession) throw new Error("Failed to initiate whisper.");

  return currentSession;
}

Problem

  1. When I establish the call using JsSIP, I’m not sure if I need to prefix the extension with “556” as would be done with a regular phone, or if I need to handle that in the SIP URI structure.

  2. When I attempt to send DTMF tone “5” to enter whisper mode after the call is established, it doesn’t appear to be recognized by the FreePBX server.

  3. When my agent is in a call with a client as an admin I want to whisper to my agent

Questions

  1. What is the correct way to implement the FreePBX spy/whisper/barge feature using JsSIP?

  2. Should I be dialing with the prefix in the SIP URI (e.g., sip:556${extension}@${domain}) or should I dial the extension normally and then use DTMF?

  3. Are there specific JsSIP settings or configurations needed for DTMF to work correctly with FreePBX?

Environment

  • JsSIP version: 3.10.1

Any guidance on the correct implementation would be greatly appreciated.

How to get FedCM API working on localhost?

I am working with fedcm api for authentication. I am using this

if ('IdentityCredential' in window) {
    // AJAX call to login
    navigator.login.setStatus('logged-in');


    // TODO handle user canceling
    const credential = await navigator.credentials.get({
        identity: {
            // Specify the IdP (or multiple IdPs, supported from Chrome 136) this Relying Party supports
            providers: [
                {
                    configURL: 'https://accounts.google.com/gsi/fedcm.json',        
                    clientId: '<my-client-id>',
                    nonce: crypto.randomUUID() 
                }
            ]
        }
    });
    console.log(credential);
}

and the client id has 2 URL’s in the authorized JS domains. One is a public https server i am running. When I test this there, it works ok and I get the credential. But when I run this on localhost:3000, and I have http://localhost:3000 in the authorized JS domain, i still get

The fetch of the id assertion endpoint resulted in a network error: ERR_FAILED
localhost/:1 Server did not send the correct CORS headers.
main.js:26 Uncaught (in promise) IdentityCredential
Error: Error retrieving a token.

how do I get this to work?