Build Your Multi-Tenant SaaS App in Days with SaaSykit Tenancy

https://picperf.io/https://laravelnews.s3.amazonaws.com/featured-images/laravel-news-sp3.png

Build Your Multi-Tenant SaaS App in Days with SaaSykit Tenancy

SaaSykit Tenancy is a multi-tenant, feature-rich Laravel-based SaaS starter kit packed with essential features to power modern SaaS applications. It offers a significant head start by providing all the foundational components you need, enabling you to focus on building your unique SaaS features rather than starting from scratch.

SaaSykit Tenancy builds on the foundation of the original SaaSykit, adding multi-tenancy support to the mix. This means you can easily create a SaaS application that can serve multiple tenants (organizations), each with its own users, subscriptions, and settings.

Building a successful multi-tenant SaaS application is no small feat. In addition to developing your core idea, you must also manage a host of other tasks, such as payment integration, product management, subscription upgrades and downgrades, landing pages, email notifications, customer education through a blog, and much more.

This is where SaaSykit Tenancy saves the day.

SaaSykit Tenancy features at a glance:

  • 👯 Multi-tenant dashboards
  • 🤑 Seat-based subscriptions (charge per user) where billing is automatically synced with payment providers as users are added or removed from the tenant
  • 💴 Flat-rate subscriptions per tenant
  • 📩 User invitations to tenants
  • 🧑‍🤝‍🧑 Team Management, adding & removing users from tenants
  • 👮‍ Tenant Roles & Permissions to control what users can do within a tenant
  • 🔌 Events for tenant actions (user added, removed, invited, etc.)

plus all the beauties from the original SaaSykit:

  • 💰 Payment provider integration (Stripe, Paddle & Lemon Squeezy)
  • 💻 Easy product, plan, discount and pricing management
  • 👩 Stunning admin panel and user dashboard (powered by FilamentPHP)
  • 🚪 Beautiful checkout process
  • 🧾 Invoice generation
  • 🗓️ Ready-to-use components (hero sections, features, testimonials, and more)
  • 🥑 Built-in user authentication and social login (Google, Facebook, X, and more) & reCAPTCHA
  • 📈 SaaS metric tracking in a beautiful dashboard
  • 🎨 Customizable landing page styling for your branding
  • 💌 Email templates and transactional emails
  • 📝 Built-in Blog with automatic open graph image generation with many themes to choose from
  • 🚧 Integrated Roadmap for your SaaS
  • 🧒 User / role management for admin panel
  • 🌍 Fully translatable and SEO-optimized
  • 🚀 One-press deployment
  • 🧪 Clean code & automated test coverage included
  • 🧑‍💻 Extensive documentation
  • and much more

Whether you’re building a multi-tenant SaaS application or a user-based platform, for yourself or a client, SaaSykit has you covered. Both SaaSykit Tenancy and SaaSykit offer robust starting points for your project, saving you significant time and effort.

Get SaaSykit now and start building your SaaS app today!


The post Build Your Multi-Tenant SaaS App in Days with SaaSykit Tenancy appeared first on Laravel News.

Join the Laravel Newsletter to get all the latest
Laravel articles like this directly in your inbox.

Laravel News

If You’re Not Using This Type of Keyboard, It’s Time to Switch

https://static1.makeuseofimages.com/wordpress/wp-content/uploads/2024/09/keychron-alice-keyboard-with-keyboard-in-background.jpg

Key Takeaways

  • Alice keyboards offer an ergonomic layout to reduce wrist movement.
  • Customizable options are available, like switches and keycaps, for comfort.
  • Switch to an Alice keyboard for increased comfort during long typing sessions.

If you’re looking for a new keyboard, there are many options. Apart from different sizes and switches, different layouts provide a more ergonomic user experience. Depending on how much you type, the comfort an Alice keyboard provides might just be reason enough to switch.

What Is an Alice Keyboard?

Alice keyboards split the traditional keyboard layout into two parts and angle the keys, so minimal wrist movement is required when typing. They’re also fairly new, with the first Alice-style keyboards designed by keyboard enthusiast Yuk Tsi in 2018. The split format in a single frame results in a much more ergonomic and comfortable typing experience than the standard "slab" keyboards we’re all used to.

Additionally, since your wrists aren’t moving as much, your shoulders and forearms feel more at ease during longer typing sessions. Note that Alice keyboards aren’t the same as split keyboards, which are divided from the middle into two separate units, each covering one side of the QWERTY layout.

Just like every other keyboard on the market, there’s a fair amount of customization, layout, and design options you can choose from when looking for an Alice keyboard. Since most, if not all, Alice keyboards you’ll find will be mechanical, you can change things like the switches and keycaps and even install mods to enhance the sound or feel of your keyboard.

The mods aren’t very different from regular keyboards either, so you can easily adapt them to the Alice format. Since they’re mostly mechanical, you’ll also be able to fix your keyboard when it doesn’t work as relatively easily.

More interestingly, Alice keyboards come in a few different options. These options vary between keyboards that change the number of keys, their alignment or orientation, and sometimes add extra features like additional buttons or dials.

How Are They Different From Regular Keyboards?

Alice keyboards, even in the QWERTY layout, differ from regular ones. It’ll take a while to get used to an Alice keyboard when first starting out, as the keys are in different places and angled differently. You’ll also try to subconsciously move your wrists, which throws you off at first.

However, once you get used to the layout, the real benefits of the Alice keyboard start kicking in. Since your wrists aren’t moving as much, you won’t feel as exhausted after long typing sessions. Additionally, if you have wrist pain or issues like carpal tunnel syndrome, an Alice keyboard can go a long way in providing comfort.

Why You Should Switch to an Alice Keyboard

The main reason you should switch to an Alice keyboard, especially if you type a lot, is comfort. They aren’t necessarily as ergonomic as the keyboard manufacturer would want you to think, but they’re easier on your wrists, forearms, and shoulders compared to a regular keyboard.

Just like gaming keyboards can be a great fit for even non-gamers, Alice keyboards are the next step. That is if you’re okay paying for them. Alice keyboards are not a popular product, so expect some markup from manufacturers. You can find some pretty good budget options, such as the Keychron V8 Max or Epomaker Cidoo, coming in at $100 and $45, respectively, at the time of writing.

There is a catch, though. Regular and fully split keyboards offer much more freedom of movement when typing, allowing you to type in different positions. Alice keyboards will work best when you’re in a certain position, meaning you won’t get to move around that much. If you often find yourself typing in different positions, an Alice keyboard might not be the best decision.

Overall, an Alice keyboard will provide better comfort during long typing sessions, with the same advantages as any regular mechanical keyboard. So, if you’re a programmer, writer, or anyone who spends a significant amount of time typing, you should definitely consider an Alice keyboard.

MakeUseOf

Different strategies for storing currency (MySQL)

https://accreditly.io/storage/blogs/110/different-strategies-for-storing-currency-values-in-mysql-1000.webp

2 min read

Published on 11th August 2023

Storing currency values in a database might seem like a trivial task, but it can become complicated due to issues like precision and rounding errors. This article will discuss several strategies for storing currency or money in a MySQL database, along with their pros and cons.

DECIMAL Data Type

The most recommended way to store currency is by using the DECIMAL data type. DECIMAL can store exact numeric data values, unlike FLOAT or DOUBLE, which are approximate numeric data types.

Here is an example of how to define a DECIMAL field for storing currency:

CREATE TABLE products (
    id INT AUTO_INCREMENT,
    price DECIMAL(19,4),
    PRIMARY KEY(id)
);

In this example, DECIMAL(19,4) means that the price field will store numbers that have up to 19 digits, including 4 digits after the decimal point.

Advantages:

Accurate to the last penny (or even beyond, depending on your specified decimal places), making it perfect for financial calculations. It is sometimes the guidelines of government financial bodies to store to 4 decimal places, even for currencies with only 2 decimal places. This is to increase precision when calculating tax.

Disadvantages:

  • DECIMAL columns take more storage space compared to FLOAT or DOUBLE.
  • Math operations with DECIMAL columns are slower compared to FLOAT or DOUBLE.

FLOAT or DOUBLE Data Types

Another way to store currency is by using FLOAT or DOUBLE data types, which can store approximate numeric data values.

CREATE TABLE products (
    id INT AUTO_INCREMENT,
    price FLOAT,
    PRIMARY KEY(id)
);

Advantages:

  • FLOAT and DOUBLE columns take less space compared to DECIMAL.
  • Math operations with FLOAT or DOUBLE are faster compared to DECIMAL.

Disadvantages:

  • FLOAT and DOUBLE cannot store exact values, which can lead to rounding errors. This makes them a poor choice for financial calculations where precision is essential.

Storing Cents Instead of Dollars

Another strategy is to store the money value in cents (or the smallest unit of your currency) as an INTEGER.

CREATE TABLE products (
    id INT AUTO_INCREMENT,
    price INT,
    PRIMARY KEY(id)
);

In this case, if the price of a product is $19.99, you would store 1999 in the price column.

Advantages:

  • Since you are storing an integer, the calculations will be exact, and you will not have to worry about rounding errors.

Disadvantages:

  • This approach requires you to manually convert between dollars (or your main currency unit) and cents in your application logic.

Choosing the right method to store currency values in your MySQL database depends on your specific use case. If exactness is critical, as it usually is in financial applications, DECIMAL is often the best choice despite its higher storage costs and slower calculations. FLOAT and DOUBLE could be used for applications where the speed of calculations is more important and a minor loss of precision can be tolerated. Storing money as cents in an INTEGER field is another approach that ensures exact calculations and could be convenient for certain applications.

Laravel News Links

Streamlining Data Processing with Laravel’s Pipeline Pattern

https://www.harrisrafto.eu/content/images/2024/08/20-08-2024-pipelines.png

The Pipeline pattern in Laravel provides an elegant way to pass data through a series of operations. This pattern is particularly useful for complex workflows or when you need to apply a sequence of transformations to your data. Let’s explore how to implement and leverage the Pipeline pattern in your Laravel applications.

Basic Pipeline Usage

Here’s a basic example of using Laravel’s pipeline:

use Illuminate\Support\Facades\Pipeline;

$result = Pipeline::send($order)
    ->through([
        ValidateOrder::class,
        ApplyDiscount::class,
        CalculateTax::class,
        ProcessPayment::class,
    ])
    ->then(fn ($order) => $order->complete());

In this example, an $order object is passed through a series of operations before being completed.

Creating Pipeline Steps

Each step in the pipeline should be a class with an __invoke method:

class ValidateOrder
{
    public function __invoke($order, $next)
    {
        // Perform validation logic
        if (!$order->isValid()) {
            throw new InvalidOrderException();
        }

        return $next($order);
    }
}

Passing Additional Parameters

You can pass additional parameters to your pipeline steps:

$result = Pipeline::send($order)
    ->through([
        ValidateOrder::class,
        [ApplyDiscount::class, $discountCode],
        [CalculateTax::class, $taxRate],
        ProcessPayment::class,
    ])
    ->then(fn ($order) => $order->complete());

In your pipeline step:

class ApplyDiscount
{
    public function __invoke($order, $next, $discountCode)
    {
        // Apply discount logic using $discountCode
        return $next($order);
    }
}

Using the Pipeline in Controllers

Implement the pipeline in your controller for clean, modular code:

class OrderController extends Controller
{
    public function store(Request $request)
    {
        $order = new Order($request->all());

        return Pipeline::send($order)
            ->through([
                ValidateOrder::class,
                SaveOrder::class,
                NotifyCustomer::class,
            ])
            ->then(fn ($order) => response()->json($order, 201));
    }
}

Error Handling in Pipelines

Handle exceptions within your pipeline steps:

class ProcessPayment
{
    public function __invoke($order, $next)
    {
        try {
            // Process payment logic
            PaymentGateway::charge($order->total);
        } catch (PaymentFailedException $e) {
            return response()->json(['error' => 'Payment failed'], 400);
        }

        return $next($order);
    }
}

Creating Reusable Pipelines

For frequently used pipelines, create a dedicated class:

class OrderProcessingPipeline
{
    public function __invoke($order)
    {
        return Pipeline::send($order)
            ->through([
                ValidateOrder::class,
                ApplyDiscount::class,
                CalculateTax::class,
                ProcessPayment::class,
            ])
            ->then(fn ($order) => $order->complete());
    }
}

Use it in your controller:

public function store(Request $request, OrderProcessingPipeline $pipeline)
{
    $order = new Order($request->all());
    return $pipeline($order);
}

Conditional Pipeline Steps

Add or remove steps based on conditions:

$steps = [ValidateOrder::class, SaveOrder::class];

if ($request->has('discount_code')) {
    $steps[] = ApplyDiscount::class;
}

$steps[] = ProcessPayment::class;

return Pipeline::send($order)
    ->through($steps)
    ->then(fn ($order) => response()->json($order, 201));

The Pipeline pattern in Laravel offers a clean, modular approach to handling complex workflows. By breaking down your logic into small, focused steps, you can create more maintainable and testable code. This pattern is particularly useful for processes like order processing, user registration, or any scenario where you need to apply a series of operations to your data in a specific order.

If this guide was helpful to you, subscribe to my daily newsletter and give me a follow on X/Twitter. It helps a lot!

Laravel News Links

Count Cells With Specific Text in Excel Using the COUNTIF Formula

https://static1.makeuseofimages.com/wordpress/wp-content/uploads/2024/09/smartphone-with-logo-of-spreadsheet-editor-microsoft-excel-on-screen-in-front-of-company-website.jpg

Key Takeaways

  • Excel’s COUNT function produces a number of cells containing any value, but only COUNTIF and COUNTIFS can use conditions to narrow your results.
  • The COUNTIF function is used for one condition, while COUNTIFS allows multiple conditions to be specified in Excel.

When you’re staring at a massive spreadsheet, counting cells manually isn’t just tedious—it’s a recipe for mistakes. Thankfully, Microsoft Excel’s COUNTIF formula swoops in to save the day. It lets you count cells based on specific text or conditions, turning your spreadsheet chaos into organized data magic.

How to Count Excel Cells Based on a Condition

Not to be confused with the SUM function, which adds up numerical values within a group of cells, the COUNT function on Excel establishes the number of cells that contain a value.

In the image above, the COUNT function produces the number of paychecks released in a single week. But in its basic form, this function will only take you so far. Its sister functions, COUNTIF and COUNTIFS, can narrow those results into only the cells that meet a condition or have a specific string.

For example, you might need to calculate only the number of checks over a certain dollar amount or paychecks related to particular people. In these cases, you’ll need to use COUNTIF and COUNTIFS.

Excel’s COUNTIF and COUNTIFS functions are exactly how they sound: they will count cells for you IF those cells meet criteria that you specify.

How to Count Cells With COUNTIF

You should use COUNTIF when you have just one condition to factor in. This function is handy when you need to count cells that exceed (or fall under) a specified number.

In the above image, an Excel sheet breaks down the payroll for two separate pay weeks. An Earned column contains a formula that calculates the amount of money a performer earned depending on a session fee and number of sessions. On a week during which a performer had no sessions, their total earned was 0, meaning they did not receive a paycheck.

Whereas the basic COUNT function would consider a 0 to be a numerical value and hence count it as a paycheck, I can instead use COUNTIF to clarify that I only want to count cells with numerical values over 0.

To use COUNTIF:

  1. Select the cell in which you’d like to display your COUNTIF output.
  2. Type in the =COUNTIF function.
  3. Within a set of parentheses, you should first include a cell range of where you want the function to look, a comma, and then what (a value, for instance) you want the function to look for.
  4. Hit Enter.

If I want to know how many paychecks were released during that week, I use the following formula:

=COUNTIF(E3:E8, >"0") 

The COUNTIF function is also useful when you have a dataset that contains repetitive values. Say I was looking at a statement of paychecks over a wide date range, and I needed to quickly count checks per person.

I could establish a column with each performer’s name, and use that cell number as the condition to count their paychecks:

=COUNTIF(A3:A9, F3) 

If I didn’t want to include a name column, I could alternatively use the exact text of their names in the COUNTIF function instead:

=COUNTIF(A3:A9, "P. King") 

Both of these functions will output the same result.

How to Count Cells With COUNTIFS

Maybe you’re set at this stage, or perhaps you need to implement additional conditions. Luckily for you, COUNTIF walked so COUNTIFS could run. Regarding the differences between COUNTIF and COUNTIFS, the gist is that COUNTIFS allows you to use multiple conditions instead of just one.

To use COUNTIFS:

  1. Select the cell in which you’d like to display your COUNTIFS output.
  2. Type in the =COUNTIFS function.
  3. A set of parentheses includes a cell range, a comma, the first value or condition you’d like the program to look for, a comma, a second cell range, a comma, and another condition.
  4. Hit Enter.

Here’s one example of what the formula might look like:

=COUNTIFS(B3:B18, G3, E3:E18, ">0") 

In this example, the spreadsheet calculates the number of paychecks per performer over multiple weeks by first specifying the person and secondarily establishing that we’re looking for any value over 0.

You might also want to not only separate paychecks per performer but also separate lower paycheck values from higher paycheck values.

In this example, which is based on a list of payroll checks released over a range of dates, the following function will establish the amount of high-value paychecks that were over $1,000 for a specific performer:

=COUNTIFS(A3:A9, "P. King", B3:B9, ">1000") 

Of course, the possibilities of the COUNTIF and COUNTIFS functions are virtually endless. You can take these functions to the next level by combining them with Excel’s wildcard symbols and nested IF statements in Excel. Ultimately, the right function for you depends on your dataset and what information you need to analyze.

MakeUseOf

🔴 Watch: Trump Town Hall LIVE!

https://www.louderwithcrowder.com/media-library/image.png?id=53590081&width=980

President Donald Trump speaks to Americans through Pennsylvania voters in the capitol city of the cradle of liberty at the New Holland Arena in Harrisburg. Fox News’ Sean Hannity moderates. The President will address voter issues including but not limited to the state of inflation in our economy thanks to Bidenomics & Kamalanomics, gun crime, abortion rights, illegal immigration at our southern border, and the overall state of democracy in America. We have special drinking game rules for you, Josh Firestine is here, and so much more! Strap in, people! We have two months to go till Election 2024 in America!

Subscribe to Louder with Crowder on Rumble! Download the app on Apple and Google Play.

RUSSIA

ABORTION

SAVE Act (Safeguard American Voter Eligibility Act)

ECONOMY/INFLATION

ENERGY

GUNS

LEGAL TROUBLES

MIDDLE EAST

TAXES

KAMALA FLIP FLOPS THUS FAR

Louder With Crowder

Laravel package for using Microsoft mail, OneDrive, Teams, Excel, Calendars and Contacts

https://opengraph.githubassets.com/64df237792f136c48c459cc2b4214519830c794a15e07213670c1933719513b1/LLoadout/microsoftgraph

Latest Version on Packagist
Total Downloads

Laravel package for using Microsoft mail, OneDrive, Teams, Excel, Calendars and Contacts

This package makes a wrapper around the Microsoft Graph API.

  1. It provides a Mail driver for Microsoft mail.
  2. It provides a storage driver for OneDrive.
  3. It provides functionality to interact with Microsoft Teams.
  4. It provides the possibility to work with Excel, making it possible to write and read Excel files.
  5. It allows you to manage calendar events.
  6. It allows you to manage contacts.
  7. It allows you to read and handle mail.

You need to register an app in the Microsoft Azure Portal to use this package. Follow the steps in the Microsoft docs:
https://docs.microsoft.com/en-us/graph/auth-register-app-v2

You can install the package via composer:

composer require lloadout/microsoftgraph

Add this to your .env file and fill it with the values you specified in Microsoft Azure Portal app registration.
If you created a multi-tenant app in Azure AD than you don’t put your tentant id into the MS_TENANT_ID variable but you set it to common.

MS_TENANT_ID=
MS_CLIENT_ID=
MS_CLIENT_SECRET=
MS_GRAPH_API_VERSION=v1.0
MS_REDIRECT_URL=https://your-url.com/microsoft/callback

The package uses OAuth and provides two routes

The first redirects you to the consent screen of Microsoft

https://your-url.com/microsoft/connect

The second is the callback url you need to specify in Microsoft Azure Portal app registration as redirect uri

https://your-url.com/microsoft/callback

The callback will fire an MicrosoftGraphCallbackReceived event, you have to listen for this event in your EventServiceProvider and store the accessData to a session variable microsoftgraph-access-data.
You can add your token store logic in a listener for this event.

public function boot()
{
    Event::listen(function (MicrosoftGraphCallbackReceived $event) {
        session()->put('microsoftgraph-access-data', $event->accessData); 
    });
}

The package will search for a session variable name microsoftgraph-access-data for establishing the connection. So
please provide this variable with your accessData as value when logging in.
For example: On login, you get your accesData from the database and store it into the session
variable microsoftgraph-access-data.

You have to provide this API permissions: Mail.send

Set the environment variable MAIL_MAILER in your .env file

MAIL_MAILER=microsoftgraph

note: make sure your from address is the address you gave the consent to

Mail::send(new YourMailable());

Mail::raw('The body of my first test message', function($message) {
    $message->to('john@doe.com', 'John Doe')->subject('A mail send via lloadout/microsoftgraph');
});

Reading and handling mail

You have to provide this API permissions: Mail.Read, Mail.ReadWrite, Mail.ReadBasic

    getMailFolders(): array|GraphResponse|mixed
    getSubFolders(id): array|GraphResponse|mixed
    getMailMessagesFromFolder([folder: string = 'inbox'], [isRead: true = true], [skip: int = 0], [limit: int = 20]): array
    updateMessage(id, data): array|GraphResponse|mixed
    moveMessage(id, destinationId): array|GraphResponse|mixed
    getMessage(id): array|GraphResponse|mixed
    getMessageAttachements(id): array|GraphResponse|mixed
    $mail = app(Mail::class);

    collect($mail->getMailFolders())->each(function($folder){
        echo $folder['displayName']."<br />";
    });

    //get all unread messages from inbox
    collect($mail->getMailMessagesFromFolder('inbox', isRead: false))->each(function($message) use ($mail){
        echo $message['subject']."<br />";
    });
        

You have to provide this API permissions: Files.ReadWrite.all

add the onedrive root to your .env file:

MS_ONEDRIVE_ROOT="me/drive/root"

All methods from the Laravel Storage facade are available. https://laravel.com/docs/8.x/filesystem#configuration

The package created a disk called onedrive. This means that you can use all the methods as described in the Laravel docs: https://laravel.com/docs/8.x/filesystem#configuration

$disk = Storage::disk('onedrive');
#create a dir
$disk->makeDirectory('Test folder');
#storing files
$disk->put('Test folder/file1.txt','Content of file 1');
$disk->put('Test folder/file2.txt','Content of file 2');
#getting files
Storage::disk('onedrive')->get('Test folder/file1.txt');

You have to provide this API permissions: Chat.ReadWrite

    getJoinedTeams(): array|GraphResponse|mixed
    getChannels(team): array|GraphResponse|mixed
    getChats(): array|GraphResponse|mixed
    getChat(id): array|GraphResponse|mixed
    getMembersInChat(chat): array|GraphResponse|mixed
    send(teamOrChat, message): array|GraphResponse|mixed

First instantiate the Teams class

$teamsClass = new Teams();

Get all the teams you are a member of ( additional permissions needed: Group.Read.All )

$joinedTeams = $teamsClass->getJoinedTeams();

Get alle the channels for a team ( additional permissions needed: Group.Read.All )

$channels = $teamsClass->getChannels($team);

Get all the chats for a user ( additional permissions needed: Chat.Read.All )

$chats = $teamsClass->getChats(); 

Get a chat by a given id ( additional permissions needed: Chat.Read.All )

$chats = $teamsClass->getChat('your-chat-id'); 

Get all the members in a channel ( additional permissions needed: ChannelMessage.Read.All )

$members = $teamsClass->getMembersInChat($chat));

Send a message to a channel ( additional permissions needed: ChannelMessage.Send )

$teamsClass->send($teamOrChat,'Hello world!');

You have to provide this API permissions: Files.ReadWrite.all

    loadFile(file): void
    loadFileById(fileId): void
    setCellValues(cellRange, values: array): void
    getCellValues(cellRange): array
    recalculate(): void
    createSession(fileId): string

First instantiate the Excel class

$excelClass = new Excel();

Load a file from OneDrive

$excelClass->loadFile('Test folder/file1.xlsx');

Load a file by its id

$excelClass->loadFileById($fileId);

Set cell values of a range

$values = ['B1' => null, 'B2' => '01.01.23', 'B3' => 3, 'B4' => '250', 'B5' => '120', 'B6' => '30 cm', 'B7' => null, 'B8' => null, 'B9' => null, 'B10' => null, 'B11' => null, 'B12' => 2];
$excelClass->setCellValues('B1:B12', $values);
$excelClass->getCellValues('H1:H20');

You have to provide this API permissions: Calendars.ReadWrite

    getCalendars(): array
    getCalendarEvents(calendar: Calendar): array
    saveEventToCalendar(calendar: Calendar, event: Event): GraphResponse|mixed
    makeEvent(starttime: string, endtime: string, timezone: string, subject: string, body: string, [attendees: array = [...]], [isOnlineMeeting: bool = false]): Event

First instantiate the Calendar class

$calendarClass = new Calendar();

Get all the calendars

$calendars = $calendarClass->getCalendars();

Get all the events for a calendar

$events = $calendarClass->getCalendarEvents($calendar);

Save an event to a calendar, the event object is a MicrosoftGraphEvent object
We made a helper function to create an event
object Calendar::makeEvent(string $starttime, string $endtime, string $timezone, string $subject, string $body, array $attendees = [], bool $isOnlineMeeting = false)

$calendarClass->saveEvent($calendar, $event);

You have to provide this API permissions: Contacts.ReadWrite

First instantiate the Contacts class

$contactsClass = new Contacts();

Get all the contacts

$contacts = $contactsClass->getContacts();

Please see CHANGELOG for more information on what has changed recently.

Please see CONTRIBUTING for details.

Please review our security policy on how to report security vulnerabilities.

The MIT License (MIT). Please see License File for more information.

Laravel News Links

Floor 496

https://theawesomer.com/photos/2024/09/floor496_t.jpgEach visit to Floor796 is like exploring world&#8217;s largest Where&#8217;s Waldo image. The website consists of a constantly-expanding isometric animation of life on the 796th floor of a huge space station. It&#8217;s packed with pop culture references, and the entire animation has been drawn by a single artist using a special browser-based editing tool. (Thanks, [&#8230;]The Awesomer

MySQL Inside: Using the PS error_log table for a quick peak!

Just thought I’d share a script I use daily and helps me redirect my attention if needed.

This is but a mere pointer, guideline and starting point in any task. I just thought I’d share and hope someone else’s day becomes slightly easier thanks to some brief investigation and command tweaking.

Now the really handy thing here is that I only hard code the router01 node name, as I’m using that as a potential endpoint (thinking cloud, XaaS, etc…) where it could also be a VIP, LBR or similar. It’s the entry point so I can query the P_S table error_log so I can get different views and act accordingly.

For example:

  • First, give me the InnoDB Cluster ordered server list so I can take a step back from my usual pains and worries, and see the architecture view. And make me type “Y” or similar to move on. Here if there were any server missing, I’d see the summary right away so I don’t really need to worry about the error_log and a good ol’ CTRL-C is struck.
    • Now, give me a list of errors that appear in the log for the last:

30 minutes

4 hours

24 hours

    And then a summary & count, so I can prioritize my investigation time.

    Some explanations as to what’s being used:

    “–login-path” is how I have configured my env for added simplicity. Feel free to use user@host:port or whatever tickles the proverbial.

    “-A” when call mysqlsh means I won’t search any cached data.

    “–sqlc” means I’ll execute SQL.

    And at the end I’m curious as to since when the instance has been recording the error_log, so I add it in, for the hell of it.

    Sharing is good, hence the script:

    $ cat error_log.sh
    
    
    
    #!/bin/bash
    
    MYSQLUSER=icadmin
    MYROUTER1=router01
    
    MYSQLSH="mysqlsh --login-path=$MYSQLUSER -h$MYROUTER1 --table -A --sqlc -e "
    
    echo
    echo "Current time:" date
    echo
    echo "Remember: login-path needs to be defined."
    MYSQLUSER=icadmin
    MYSQLSH="mysqlsh --login-path=$MYSQLUSER -h$HOSTNAME --tabbed -A --sqlc -e "
    MYSQLLOGIN="mysqlsh --login-path=$MYSQLUSER -h"
    
    MYROUTER1=$MYSQLSH "select min(address) from mysql_innodb_cluster_metadata.routers" | grep -v address
    MYROUTER2=$MYSQLSH "select max(address) from mysql_innodb_cluster_metadata.routers" | grep -v address
    HOST1=$MYSQLSH "select MEMBER_HOST from performance_schema.replication_group_members where MEMBER_ROLE = 'PRIMARY';" | grep -v MEMBER
    HOST2=$MYSQLSH "select min(MEMBER_HOST) from performance_schema.replication_group_members where MEMBER_ROLE != 'PRIMARY';" | grep -v MEMBER
    HOST3=$MYSQLSH "select max(MEMBER_HOST) from performance_schema.replication_group_members where MEMBER_ROLE != 'PRIMARY';" | grep -v MEMBER
    echo
    echo "We have the following information: "
    echo " "$MYROUTER1" & "$MYROUTER2
    echo " "$HOST1" & "$HOST2" & "$HOST3
    echo " "$HOSTNAME" is the current host."
    echo
    read -p "Continue? (Y/N): " confirm && [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]] || exit 1
    
    
    echo ----------------------------------------------------------------------------------------------------------------------------------------------------------------
    echo
    echo $HOST1
    MYSQLSH="mysqlsh --login-path=$MYSQLUSER -h$HOST1 --table -A --sqlc -e "
    
    echo "Errors in the last 30 mins:"
    $MYSQLSH "select * from performance_schema.error_log where logged > (NOW() - INTERVAL 30 MINUTE)"
    echo
    echo "Errors in the last 4 hours:"
    $MYSQLSH "select * from performance_schema.error_log where logged > (NOW() - INTERVAL 4 HOUR)"
    echo
    echo "Errors for the last 24 hours:"
    $MYSQLSH "select * from performance_schema.error_log where logged > (NOW() - INTERVAL 1 DAY)"
    echo
    echo "Summary of the latest errors for the last 24h in the error_log:"
    $MYSQLSH "select count(*) from performance_schema.error_log where logged > (NOW() - INTERVAL 1 DAY)" $MYSQLSH "select ERROR_CODE, SUBSYSTEM, DATA, count() from performance_schema.error_log where logged > (NOW() - INTERVAL 1 DAY) group by ERROR_CODE, SUBSYSTEM, DATA"
    echo
    echo "Summary of error log:"
    $MYSQLSH "select min(LOGGED) as Earliest, max(LOGGED) as Last, count(*) as Count from performance_schema.error_log "
    echo
    echo ----------------------------------------------------------------------------------------------------------------------------------------------------------------
    echo
    echo $HOST2
    MYSQLSH="mysqlsh --login-path=$MYSQLUSER -h$HOST2 --table -A --sqlc -e "
    echo
    echo "Current time:"date
    echo
    echo "Errors in the last 30 mins:"
    $MYSQLSH "select * from performance_schema.error_log where logged > (NOW() - INTERVAL 30 MINUTE)"
    echo
    echo "Errors in the last 4 hours:"
    $MYSQLSH "select * from performance_schema.error_log where logged > (NOW() - INTERVAL 4 HOUR)"
    echo
    echo "Errors for the last 24 hours:"
    $MYSQLSH "select * from performance_schema.error_log where logged > (NOW() - INTERVAL 1 DAY)"
    echo
    echo "Summary of the latest errors for the last 24h in the error_log:"
    $MYSQLSH "select count(*) from performance_schema.error_log where logged > (NOW() - INTERVAL 1 DAY)" $MYSQLSH "select ERROR_CODE, SUBSYSTEM, DATA, count() from performance_schema.error_log where logged > (NOW() - INTERVAL 1 DAY) group by ERROR_CODE, SUBSYSTEM, DATA"
    echo
    echo "Summary of error log:"
    $MYSQLSH "select min(LOGGED) as Earliest, max(LOGGED) as Last, count(*) as Count from performance_schema.error_log "
    echo
    
    echo ----------------------------------------------------------------------------------------------------------------------------------------------------------------
    echo
    echo $HOST3
    MYSQLSH="mysqlsh --login-path=$MYSQLUSER -h$HOST3 --table -A --sqlc -e "
    echo
    echo "Current time:"date
    echo
    echo "Errors in the last 30 mins:"
    $MYSQLSH "select * from performance_schema.error_log where logged > (NOW() - INTERVAL 30 MINUTE)"
    echo
    echo "Errors in the last 4 hours:"
    $MYSQLSH "select * from performance_schema.error_log where logged > (NOW() - INTERVAL 4 HOUR)"
    echo
    echo "Errors for the last 24 hours:"
    $MYSQLSH "select * from performance_schema.error_log where logged > (NOW() - INTERVAL 1 DAY)"
    echo
    echo "Summary of the latest errors for the last 24h in the error_log:"
    $MYSQLSH "select count(*) from performance_schema.error_log where logged > (NOW() - INTERVAL 1 DAY)" $MYSQLSH "select ERROR_CODE, SUBSYSTEM, DATA, count() from performance_schema.error_log where logged > (NOW() - INTERVAL 1 DAY) group by ERROR_CODE, SUBSYSTEM, DATA"
    echo
    echo "Summary of error log:"
    $MYSQLSH "select min(LOGGED) as Earliest, max(LOGGED) as Last, count(*) as Count from performance_schema.error_log "
    echo
    echo "End!"

    Planet MySQL