How Colored Pencils are Made

How Colored Pencils are Made

Link

We’ve seen footage of pencils being made before, but the guys at Faber-Castell want us to know that their process is the best. Watch as they make black leads from graphite and clay, and colorful ones from powders and wax, then sandwich them into wooden shells, and paint them to match.

via The Awesomer
How Colored Pencils are Made

ClamAV Anti-Virus Validator for Laravel

ClamAV Anti-Virus Validator for Laravel

ClamAV Validator is a Laravel package by Krishnaprasad MG that provides a custom virus validator based on ClamAV antivirus scanner for file uploads.

Some setup is required to use this package—you need to install ClamAV antivirus scanner on your server to make this package work.

Once you have configured your servers, you use this rule like any other validation rule:

$request->validate(['upload' => 'clamav']); 

To learn more about the underlying anti-virus engine, check out the ClamAV antivirus website.

You can learn more about the ClamAV Validator package, get full installation instructions, and view the source code on GitHub at sunspikes/clamav-validator.


Filed in: News


Enjoy this? Get Laravel News delivered straight to your inbox every Sunday.

No Spam, ever. We’ll never share your email address and you can opt out at any time.

via Laravel News
ClamAV Anti-Virus Validator for Laravel

The Empire Strikes Axe

The Empire Strikes Axe

Link

Star Wars tribute band Galactic Empire bangs out an awesome hard rock cover version of John Williams’ The Imperial March, giving the Empire’s most iconic theme song an appropriately razor-sharp edge to accompany all of their evil deeds. Even Emperor Palpatine approves, and he’s not easy to please.

via The Awesomer
The Empire Strikes Axe

Video Platforms Parser

Video Platforms Parser

The Video Platforms parser PHP package by @chojnicki is an easy-to-use SDK for multiple platforms like YouTube, Dailymotion, Facebook, and more.

The API usage is simple, you can get video info like this:

$info = $parser->get('https://www.youtube.com/watch?v=jofNR_WkoCE'); 

Here’s some sample output from Dailymotion, which is similar data you’ll see from any supported platform:

>>> \VideoPlatformsParser::get('https://www.dailymotion.com/video/x6qac05?playlist=x5xlnc'); => [ "id" => "x6qac05", "platform" => "dailymotion", "title" => "Simon Pegg on His Most Iconic Characters", "description" => "Simon Pegg breaks down his favorite and most iconic characters, including Tim from "Spaced," Shaun from "Shaun of the Dead," Nicholas Angel from "Hot Fuzz," Gary King from "The World's End," Scotty in "Star Trek," Unkar Plutt in "Star Wars: The Force Awakens," and Benji Dunn in the "Mission: Impossible" movies. Simon stars in "Mission: Impossible - Fallout," out in theaters July 27th.", "thumbnail" => "https://s1.dmcdn.net/v/OGOr51TU0A1fJD7He", "tags" => [ "celebrity", "iconic", "mission impossible", "simon pegg", "Season: Season 1", "Series: Iconic Characters", ], "api" => true, ] 

At the time of writing this package supports the following platforms:

  • YouTube
  • Dailymotion
  • Facebook
  • LiveLeak
  • CDA
  • Vimeo

You can learn more about this package, get full installation instructions, and view the source code on GitHub at chojnicki/video-platforms-parser.


Filed in: News


Enjoy this? Get Laravel News delivered straight to your inbox every Sunday.

No Spam, ever. We’ll never share your email address and you can opt out at any time.

via Laravel News
Video Platforms Parser

Getting started with Laravel APIs Part 2: Stoplight Studio

stoplight studio logo

stoplight studio logo

Designing an API can be easy. As long as you approach it in a logical way, and take each part separately, so you do not over complicate the process. For this article I am going to take a theoretical API and walk through how I would design it.

What we are building?

building blocks

building blocks

In this series we will be building a Training Log API, what is this? A training log is a simple referencing platform which allows individuals, or organisations, to make a log of what training has been completed as well as marking attendance for training courses.

Possibly one of the first steps for anyone creating an API is figuring out some sort of business logic, what will this system do extactly. These can theen be broken down into user stories/epics/features and go through some level of project management flow to create what will become our sprints.

Lets figure out what we need

As this is a tutorial, I will not be going completely indepth with what this system will do – it will be a trimmed down API to demonstrate a process. We can break down our requirmeents in a simple list at this stage. I like to think in a behavioural way at this stage:

  • As a user, I must be able to login and register
  • As a user, I must be able to see courses available
  • As a user, I must be able to enrol on a course
  • As a user, I must be able to attend training sessions as part of a course
  • As a user, I must be able to see my progress on a course and my completed courses

These are some pretty basic requirements for our training log API. However, even these simple requiremnts will give us a good insight into how we should approach designing our API.

Stoplight is a fantastic tool for designing your API, and the Models within your API. It has a great user interface, and has the benefit of creating openapi spec deocuments for you. For example:

model designer on stoplight studio

model designer on stoplight studio

Which in turn outputs:

title: User type: object description: An entity that can log into our system properties:  id:  type: integer  uuid:  type: string

For this style system a User is always one of the first things that I build, for the purpose of saving reading space I am ommiting none crucial fields from the models.

One we have an entity that is able to access our system we can start to define the connecting models which will in turn define what our API can do.

Breaking down our requirements

Going through our requirements above, it is easy to see how we could break down our resources and start to map relationships between them. Below I will break down the process:

As a user, I must be able to login and register

So a User model will need to be able to access our system – this part is easy enough to understand.

As a user, I must be able to see courses available

A User needs to be able to see available courses. So we will need a Course model and one of the requirements is that we need to see only available ones: so this will want a boolean flag to mark availability which we can apply through the API.

As a user, I must be able to enrol on a course

So we have a User and we have Courses. Now we need to enrol on them. The easiest option here would be to create a many to many relationship through a pivit table in Laravel it would look like:

<?php class User extends Model {  public function courses()  {  return $this->belongsToMany(Course::class);  } } class Course extends Model {  public function users()  {  return $this->belongsToMany(User::class);  } }

This, however, is not what I am going to do. For this I will create a new model called Enrolment which will capture both User and Course but will give us more extensibility than a standard pivot relation.

As a user, I must be able to attend training sessions as part of a course

So each course will want to be broken into sections, which will have an order to them. From this you can also gather that we will want some meta information to tag onto courses and sections. From working in EdTech previously, I know that a course/section will need to have topics that it covers so that an end user can figure out what they will learn on this course and if they want to do it.

To mark attendance, a simple pivot relation will suffice here for now. I do not think this is the time to think about CQRS and Event Sourcing – while it will give great control and insight into the data, it will be over engineering for what we need.

As a user, I must be able to see my progress on a course and my completed courses

So Users want to Enrol onto a Course, which will be tagged with Topics, which will allow them to Attend the many Sections and see progress along the way.

When you look at the feature requirements and the outputted list, there are other areas where improvements could be added – however it is easy to get carried away with yourself and over engineer an API at this point.

So, Stoplight Studio – putting the piecse together

Below you will see some examples (screenshots) and markup which you can use in your projects:

User Model

title: User type: object description: An entity that can log into our system properties:  id:  type: integer  uuid:  type: string

Course Model

title: Course type: object description: An Entity that Users can Enrol onto properties:  id:  type: integer  uuid:  type: string

Enrolment Model

title: Enrolment type: object description: A entity that allows the linking of Users to Courses properties:  id:  type: integer  uuid:  type: string  user:  $ref: ./user.v1.yaml  course:  $ref: ./course.v1.yaml

Section Model

title: Section type: object properties:  id:  type: integer  uuid:  type: string  course:  $ref: ./course.v1.yaml  order:  type: integer description: An entity that defines parts of a Course

Conclusion

Breaking down requirements can take a little bit of extra thought, but using a tool like Stoplight to visualise the information is very handy – and actually makes the task a lot nicer!

In the next part to this series we will be starting some Laravel code going through how we should set up Laravel for an API, and what changes we might want to consider.

via Laravel News Links
Getting started with Laravel APIs Part 2: Stoplight Studio

How To Export Data In Excel and CSV In Laravel 6 | maatwebsite/excel 3.1

How To Export Data In Excel and CSV In Laravel 6 | maatwebsite:excel 3.1

In this tutorial, we will see How To Export Data In Excel and CSV In Laravel 6 | maatwebsite/excel 3.1. If you want to up and running with basic laravel functionality, then go to my other article on this web blog called Laravel 6 Crud Example From ScratchIf you want to Generate PDF In Laravel, then check out Laravel 6 Generate PDF From View Example. For this example, we use the package called maatwebsite/excel version 3.1. So, our Laravel 6 and maatwebsite/excel 3.1.

Export Data In Excel and CSV in Laravel 6

When using the package in your application, it’s good to understand how the package functions behind the scenes. Following the behind-the-scenes will make you feel more comfortable and confident using the maximum potential of the tool.

Laravel Excel 3.1

🚀 Laravel Excel is intended at being Laravel-flavoured PhpSpreadsheet: a simple, but an elegant wrapper around PhpSpreadsheet to simplify the exports and imports.

🔥 PhpSpreadsheet is the library written in pure PHP and providing the set of classes that allow us to read from and to write to different type of spreadsheet file formats, like Excel and LibreOffice Calc.

Laravel Excel Features

  1. We can easily export collections to Excel.
  2. We can export queries with automatic chunking for better performance.
  3. We can queue exports for better performance.
  4. We can easily export Blade views to Excel.
  5. We can easily import to collections.
  6. We can read the Excel file in chunks.
  7. We can handle the import inserts in batches.

Requirements

  1. PHP: ^7.0
  2. Laravel: ^5.5
  3. PhpSpreadsheet: ^1.6
  4. PHP extension php_zip enabled
  5. PHP extension php_xml enabled
  6. PHP extension php_gd2 enabled

Step 1: Installation

Require the following package in the composer.json of your Laravel 6 project. The following command will download the package and PhpSpreadsheet.

composer require maatwebsite/excel
➜  laravel6 git:(master) ✗ composer require maatwebsite/excel
Using version ^3.1 for maatwebsite/excel
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 4 installs, 0 updates, 0 removals
  - Installing markbaker/matrix (1.1.4): Downloading (100%)
  - Installing markbaker/complex (1.4.7): Downloading (100%)
  - Installing phpoffice/phpspreadsheet (1.9.0): Downloading (100%)
  - Installing maatwebsite/excel (3.1.17): Downloading (100%)
phpoffice/phpspreadsheet suggests installing mpdf/mpdf (Option for rendering PDF with PDF Writer)
phpoffice/phpspreadsheet suggests installing tecnickcom/tcpdf (Option for rendering PDF with PDF Writer)
phpoffice/phpspreadsheet suggests installing jpgraph/jpgraph (Option for rendering charts, or including charts with PDF or HTML Writers)
Writing lock file
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Discovered Package: barryvdh/laravel-dompdf
Discovered Package: facade/ignition
Discovered Package: fideloper/proxy
Discovered Package: laravel/tinker
Discovered Package: laravel/ui
Discovered Package: maatwebsite/excel
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Package manifest generated successfully.
➜  laravel6 git:(master) ✗

Step 2: Configure package

The Maatwebsite\Excel\ExcelServiceProvider is auto-discovered and registered by default.

If you want to register by yourself, then add the ServiceProvider in config/app.php:

'providers' => [
    /*
     * Package Service Providers...
     */
    Maatwebsite\Excel\ExcelServiceProvider::class,
]

Excel facade is auto-discovered.

If you want to add it manually, add a Facade in config/app.php:

'aliases' => [
    ...
    'Excel' => Maatwebsite\Excel\Facades\Excel::class,
]

If you want to publish a config, run the vendor publish command:

php artisan vendor:publish --provider="Maatwebsite\Excel\ExcelServiceProvider"
➜  laravel6 git:(master) ✗ php artisan vendor:publish --provider="Maatwebsite\Excel\ExcelServiceProvider"
Copied File [/vendor/maatwebsite/excel/config/excel.php] To [/config/excel.php]
Publishing complete.
➜  laravel6 git:(master) ✗

This will create the new config file named config/excel.php.

Step 3: Create model and migration files

Type the following command.

php artisan make:model Disneyplus -m

Now, go to the [timestamp].create_disneypluses_table.php file and add the columns.

public function up()
{
        Schema::create('disneypluses', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->string('show_name');
            $table->string('series');
            $table->string('lead_actor');
            $table->timestamps();
        });
}

Now, migrate the database using the following command.

php artisan migrate

Step 4: Create a controller and routes

Next step is to create a DisneyplusController.php file.

php artisan make:controller DisneyplusController

Now, add the two routes inside the routes >> web.php file.

// web.php

Route::get('disneyplus', 'DisneyController@create')->name('disneyplus.create');
Route::post('disneyplus', 'DisneyController@store')->name('disneyplus.store');

Now, create two methods inside the DisneyplusController.php file.

<?php

// DisneyplusController.php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Disneyplus;

class DisneyplusController extends Controller
{
    public function create()
    {

    }

    public function store()
    {
        
    }
}

Step: 5 Create a form blade file for input the data

Now, inside the views folder, create one file called form.blade.php file. Add the following code.

@extends('layout')

@section('content')
<style>
  .uper {
    margin-top: 40px;
  }
</style>
<div class="card uper">
  <div class="card-header">
    Add Disneyplus Shows
  </div>
  <div class="card-body">
    @if ($errors->any())
      <div class="alert alert-danger">
        <ul>
            @foreach ($errors->all() as $error)
              <li></li>
            @endforeach
        </ul>
      </div><br />
    @endif
      <form method="post" action="">
          <div class="form-group">
              @csrf
              <label for="name">Show Name:</label>
              <input type="text" class="form-control" name="show_name"/>
          </div>
          <div class="form-group">
              <label for="price">Series :</label>
              <input type="text" class="form-control" name="series"/>
          </div>
          <div class="form-group">
              <label for="quantity">Show Lead Actor :</label>
              <input type="text" class="form-control" name="lead_actor"/>
          </div>
          <button type="submit" class="btn btn-primary">Create Show</button>
      </form>
  </div>
</div>
@endsection

Step 6: Store data in the database

Now, we will write the two functions inside the DisneyplusController.php file.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Disneyplus;

class DisneyplusController extends Controller
{
    public function create()
    {
        return view('form');
    }

    public function store(Request $request)
    {
        $validatedData = $request->validate([
            'show_name' => 'required|max:255',
            'series' => 'required|max:255',
            'lead_actor' => 'required|max:255',
        ]);
        Disneyplus::create($validatedData);
   
        return redirect('/disneyplus')->with('success', 'Disney Plus Show is successfully saved');
    }
}

So, in the above file, first, we have shown the form file, and then inside the store function, we check for validation and then store the data into the database.

Also, add the fillable fields inside the Disneyplus.php model file.
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Disneyplus extends Model
{
    protected $fillable = ['show_name', 'series', 'lead_actor'];
}

Now, go to this route: https://ift.tt/2Lvph4g or http://localhost:8000/disneyplus

You will see one form. Try to save the data, and if everything in the code is right, then, you will see one entry in the database.

Step: 7 Create a view file for display the data.

Before we create a view file, we need to add one route inside the web.php.

// web.php

Route::get('disneyplus/list', 'DisneyplusController@index')->name('disneyplus.index');

Now, create a view file called list.blade.php file. Add the following code.

@extends('layout')
@section('content')
<table class="table table-striped">
  <thead>
    <th>ID</th>
    <th>Show Name</th>
    <th>Series</th>
    <th>Lead Actor</th>
    <th>Action</th>
  </thead>
  <tbody>
    @foreach($shows as $show)
    <tr>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
    </tr>
    @endforeach
  </tbody>
</table>
@endsection

Now, add the code inside the index() function of DisneyplusController.php file.

public function index()
{
        $shows = Disneyplus::all();

        return view('list', compact('shows'));
}

Now, go to the http://laravel6.test/disneyplus/list or http://localhost:8000/disneyplus/list

You will see the listing of the shows.

Step 8: Create Exports class

You may do this by using the make:export command.

php artisan make:export DisneyplusExport --model=Disneyplus

The file can be found in app/Exports directory.

The file DisneyplusExport.php is following.

<?php

namespace App\Exports;

use App\Disneyplus;
use Maatwebsite\Excel\Concerns\FromCollection;

class DisneyplusExport implements FromCollection
{
    /**
    * @return \Illuminate\Support\Collection
    */
    public function collection()
    {
        return Disneyplus::all();
    }
}

If you prefer to create a export manually, you can build the following in app/Exports.

Step 9: Write the export function

Inside the DisneyplusController.php file, add the following code.

// DisneyplusController.php

use App\Disneyplus;
use App\Exports\DisneyplusExport;
use Maatwebsite\Excel\Facades\Excel;

public function export() 
{
        return Excel::download(new DisneyplusExport, 'disney.xlsx');
}

So, our final file looks like below.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Disneyplus;
use App\Exports\DisneyplusExport;
use Maatwebsite\Excel\Facades\Excel;

class DisneyplusController extends Controller
{
    public function create()
    {
        return view('form');
    }

    public function store(Request $request)
    {
        $validatedData = $request->validate([
            'show_name' => 'required|max:255',
            'series' => 'required|max:255',
            'lead_actor' => 'required|max:255',
        ]);
        Disneyplus::create($validatedData);
   
        return redirect('/disneyplus')->with('success', 'Disney Plus Show is successfully saved');
    }

    public function index()
    {
        $shows = Disneyplus::all();

        return view('list', compact('shows'));
    }

    public function export() 
    {
        return Excel::download(new DisneyplusExport, 'disney.xlsx');
    }
}

Finally, add the route to be able to access the export:

// web.php

Route::get('export', 'DisneyplusController@export');

Also, add the link to the Export inside the list.blade.php file.

@foreach($shows as $show)
    <tr>
      <td></td>
      <td></td>
      <td></td>
      <td></td>
      <td><a href="">Export</a></td>
    </tr>
@endforeach

Okay, now finally go to the https://ift.tt/32B5QMQ, and now you can see one link called Export.

Click on the Export link, and you will see the disney.xlsx file inside your Download folder.

Exporting collections in CSV in Laravel 6

By default, the export format is determined by the extension of the file.

public function export() 
{
        return Excel::download(new DisneyplusExport, 'disney.csv');
}

It will download the CSV file.

If you want to configure the export format explicitly, you can pass it through as 2nd parameter.

You can find more details about export in different formats on this link.

Finally, How To Export Data In Excel and CSV In Laravel 6 | maatwebsite/excel 3.1 is over.

The post How To Export Data In Excel and CSV In Laravel 6 | maatwebsite/excel 3.1 appeared first on AppDividend.

via Planet MySQL
How To Export Data In Excel and CSV In Laravel 6 | maatwebsite/excel 3.1

Intellectual Property Is Neither Intellectual, Nor Property: Discuss

Well over a decade ago I tried to explain why things like copyright and patents (and especially trademarks) should not be considered "intellectual property," and that focusing on the use of "property" helped to distort nearly every policy debate about those tools. This was especially true among the crowd who consider themselves "free market supporters" or, worse, "against government regulations and handouts." It seemed odd to me that many people in that camp strongly supported both copyright and patents, mainly by pretending they were regular property, while ignoring that both copyrights and patents are literally centralized government regulations that involve handing a monopoly right to a private entity to prevent competition. But supporters seemed to be able to whitewash that, so long as they could insist that these things were "property", contorting themselves into believing that these government handouts were somehow a part of the free market.

For years I got strong pushback from people when I argued that copyright and patents were not property — and a few years ago, I modified my position only slightly. I pointed out that the copyright or the patent itself can be considered property (that is, the "right" that is given out by the government), but not the underlying expression or invention that those rights protect. Indeed, these days I think so much of the confusion about the question of "property", when it comes to copyright and patents, is that so many people (myself included at times) conflate the rights given by the government with the underlying expression or invention that those rights protect. In other words, the government-granted monopoly over a sound recording does have many aspects that are property-like. But the underlying song does not have many property-like aspects.

Either way, it’s great to see the Niskanen Center, a DC-think tank that continually does good work on a variety of subjects, has decided to try to re-climb that mountain to explain to "free market" and "property rights" supporters why "intellectual property is not property." If you’ve been reading Techdirt for any length of time, most of the arguments won’t surprise you. However, it is a very thoughtful and detailed paper that is worth reading.

Imagine two farms sitting side by side in an otherwise virgin wilderness, each of them homesteaded by a husband-and-wife couple (let’s call them Fred and Wilma and Barney and Betty) — two parcels of newly created private property appropriated from the commons by productive labor. One day, as Fred and Wilma are both working outside, they both notice Betty walking through the orchard of apple trees that Barney and she had planted some years back and which are now just ready to bear fruit for the first time. As Betty picks some of the first ripening apples to use in baking a pie, she sings an enchantingly lovely ballad that she and Barney had made up together back when they were courting. For the rest of the day Wilma can’t stop thinking about that beautiful song, while Fred can’t stop thinking about those trees full of delicious apples. That night Wilma sings the song to her baby daughter as a lullaby. Fred, meanwhile, sneaks over onto Barney and Betty’s property, picks a sack full of apples, tiptoes back to his property and proceeds to eat the lot of them, feeding the cores to his pigs before heading back inside.

Do you think that Fred and Wilma both did something wrong? Are they both thieves? Did both of them violate Barney and Betty’s rights? After all, Fred stole their apples, and Wilma “stole” their song — that is, she sang it to someone else without asking for permission. If you’re having trouble seeing Fred and Wilma’s actions as morally equivalent, it’s because of a fundamental difference between the two types of “property” they took from Barney and Betty.

That fundamental difference is that Barney and Betty’s song, like all ideal objects, is a nonrivalrous good. In other words, one person’s use or consumption of it in no way diminishes the ability of others to use or consume it. As expressed with characteristic eloquence by Thomas Jefferson (who perhaps not coincidentally viewed patents and copyrights with skepticism), the “peculiar character [of an idea] is that no one possesses the less, because every other possesses the whole of it. He who receives an idea from me, receives instruction himself without lessening mine; as he who lights his taper at mine, receives light without darkening me.”

By contrast, physical objects like apples are rivalrous: Once Fred and his pigs had finished devouring the ones Fred stole, they were gone and nobody else could consume them. Even when physical objects aren’t physically consumed by their owners — think paintings or plots of land — there is still unavoidable rivalry in using, enjoying, and disposing of them. The owner exercises that control over the owned object, and therefore nobody else does.

This is why it’s clear that Fred inflicted harm on his neighbors, since he took the fruit that they grew and now they don’t have it anymore. But Barney and Betty still have their song; the fact that Wilma sang it did nothing to prevent them from singing it anytime they want to. So, if Wilma did harm to Barney and Betty, what exactly is it?

The whole paper is really worth reading, and digs in on how and why people create, the nature of externalities in the creative process, and what actual data shows on the incentives of copyright and patents in driving innovation and creativity. The paper also digs deep on how excessive monopoly rights vastly hinder follow-on creativity and innovation (which is how most innovation and creativity come about in the first place).

In the case of copyright, excessive internalization is an impediment to the process of borrowing that is essential for the growth of creative works. While each artist may contribute new ideas to the cultural landscape, their contributions are based on the previous body of work. We all begin as consumers of ideas — and then some of us go on to create new ones. Take the case of Star Wars. The Jedi, Darth Vader, and the Death Star were all new in 1977, but George Lucas relied heavily on older ideas to make them possible. It is common knowledge that Lucas borrowed from Joseph Campbell’s Hero With a Thousand Faces when crafting the hero’s journey of Luke Skywalker. But the borrowing didn’t stop there. The famous opening crawl is virtually identical to those at the beginning of episodes of Flash Gordon Conquers the Universe. Telling the story from the perspective of two lowly characters, the droids R2-D2 and C-3P0, was inspired by Kurosawa’s The Hidden Fortress — something Lucas freely admits.

But while Lucas’s borrowing was permissible under copyright law, other borrowing is not, as current law gives rights holders control over broadly defined “derivative works.” A number of Star Wars fan films have been shuttered or severely limited in their scope (mostly by prohibiting commercialization) due to threats of litigation by Disney. The genre of fan fiction is a legal gray area, with many tests to determine whether it constitutes fair use, including commercialization and how “transformative” the work is. While the vast majority of these works will never amount to much, their existence is more tolerated than established as a clear-cut case of fair use. A more aggressively enforced copyright regime would almost certainly be the end of most fan fiction.

Thankfully, the paper also takes on the "fruits of our labor" view of both copyright and patents and why that doesn’t make much sense either.

The idea that people should be able to enjoy the fruits of their labor has clear intuitive appeal, but its invocation as a justification for stopping other people from making use of your ideas without your permission suffers a fatal difficulty: The argument proves far too much. Indeed, the problem goes beyond the widely understood “negative space” of intellectual creations that stand outside of patent and copyright protection: scientific discoveries, fashion, comedy, etc. Given that every new business venture starts with an idea, why shouldn’t every first entrant in a new industry be able to claim a monopoly? Or, for that matter, why not every first entry in a geographic market? If someone has the bright idea that their hometown needs a Thai restaurant and succeeds in making a go of it, why shouldn’t she be able to prevent competitors from coming in to poach her good idea — at least for a couple of decades? On the other hand, given that every new idea is in some way adapted from earlier ideas, why shouldn’t those first entrants in new industries and new markets be seen as “thieves” and “pirates” who are infringing on earlier ideas? Once you really start working through the implications, the whole argument collapses in a hopeless muddle.

The problem is this: The claim that enjoying the fruits of one’s intellectual labor entitles you to stop competitors has no inherent limiting principle, and thus the claim can be extended headlong into absurdity — as indeed it frequently has been. Of course, one can impose limits on the claim, but those limits have to be based on other principles — in particular, some sense of relative costs and benefits. But now we’re doing policy analysis and the case-specific comparison of costs and benefits, at which point the grandiose-sounding claim that patent and copyright law combat injustice shrivels and fades.

The paper then suggests some reforms for both copyright and patent law that seem quite reasonable. On copyright, they suggest reducing terms, requiring registration, limiting infringement to commercial exploitation, expanding fair use, narrowing derivative works, and ending anti-circumvention (a la DMCA 1201). These are all good suggestions, though the "commercial exploitation" one is one that sounds good but is often hard to implement, because what is and what is not "commercial exploitation" can be somewhat gray and fuzzy at times. But the intent here is sound.

On patents, the paper suggestions are to eliminate both software and business method patents, greatly tighten eligibility requirements, and no infringement in cases of independent invention. To me, as I’ve argued, the independent invention point is the most important. Indeed, I’ve argued that we should go further than just saying that independent invention is a defense against infringement. Instead, we should note that independent invention is a sign of obviousness, meaning not only that the second invention isn’t infringing, but that the initial patent itself should likely be invalid, as patents are only supposed to be granted if the idea is not obvious to those skilled in the art.

All in all, this is a great and thorough paper, especially for those who really want to insist that copyrights and patents should be treated like traditional property, and position themselves as supporters of "free markets." I fully expect — as I’ve experienced in the past — that those people will not engage seriously with these arguments and will rage and scream about them, but it’s still important to make these points.

Permalink | Comments | Email This Story

via Techdirt
Intellectual Property Is Neither Intellectual, Nor Property: Discuss

Track successful email deliveries, clicks and opens in your Laravel PHP application with Mailgun

Posted on Sep 9, 2019.

Table Of Contents

Source if you want to download the final project

While building ContestKit there was a feature I wanted to allow users to know if the emails that were sent to the winners were delivered successfully. Thankfully this feature is relatively easy to add because of Mailgun’s amazing API. Let’s create a new Laravel application and get started.

Set up your Laravel Application

To get this process started I always use the Laravel installer to start with a fresh install. (I will be using 6.0.1 which is the most recent at the time of this writing) This will get a Laravel application installed, generate your .env file and install your composer dependencies. Once that is installed we’re going to install the Laravel UI package to quickly scaffold the auth views for our app. This will generate our login and registration views.

composer require laravel/ui

Once that is installed run

php artisan ui vue --auth

Set up Mailgun

Next, you’ll need a Mailgun account. If you don’t have one, you can sign up for a free account here that gives you 10,000 free email sends a month. Once you have created your account. Once you have your account all set up, head over here to get your API information. You’re going to need your Private API key and the domain. Once you have that, open your .env and let’s add some information so our application knows to use Mailgun as our mail provider.

MAIL_DRIVER=mailgun MAILGUN_DOMAIN=yoursendingdomain.com MAILGUN_SECRET=private-api-key

Since we’re going to be using Mailgun as our email driver, we need to make sure we satisfy all the driver prerequisites, in this case, we need to install Guzzle Http so run

composer require guzzlehttp/guzzle.

We’re going to need to come back to Mailgun, later on, to set up some webhooks, but until then let’s get our application sending some emails, creating database records corresponding to the emails sent and get the controllers to handle the incoming webhook information from Mailgun.

Set up our database

Next, let’s get our email message model set up. In your console, run the following command.

$ php artisan make:model Message -m

This command will make a model for you in App/Model call Message.php and it will also make a migration for a table called messages. Let’s open our migration and get our DB schema in. The migration should be called ${datatime-stamp}_create_messages_table.php.

<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateMessagesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('messages', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('message_id')->index(); $table->morphs('sender'); $table->dateTime('delivered_at')->nullable(); $table->dateTime('failed_at')->nullable(); $table->integer('opened')->default(0); $table->integer('clicked')->default(0); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('messages'); } }

What this table is going to do is track the outbound email message for us. We’re going to use a sender morphTo relationship so that we can attach outbound messages to any model in our app that we want. This will translate into being able to attach the welcome email we send to the user we sent it to. The $table->morphs('sender'); will create two columns for us, a sender_type which will be represented by the model that sent the message, the second will be the sender_id which will represent the id of the Model.

From there we will store the unique id generated for the email message. Mailgun will send this message id when it sends webhook events. We ask Mailgun to notify us of four different events.

  • The successful delivery of a message.
  • Any clicks on links in the email (if there are any links).
  • Every time the email is opened for that email.
  • Any permanently failed deliveries.

This failed delivery event is triggered when Mailgun attempts to send the email several but the receiving email service does not accept it. This usually results from a fake/bad email or your email was blocked by the mail service you were sending it to.

Next, let’s add the message relationship to our App\User model. Open up that and add the following.

<?php namespace App; use App\Message; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; class User extends Authenticatable { use Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; /** * The attributes that should be cast to native types. * * @var array */ protected $casts = [ 'email_verified_at' => 'datetime', ]; public function message() { return $this->morphOne(Message::class, 'sender'); } }

This will allow us to attach the outbound message on the user.

Next, let’s define the inverse relationship on the App\Message model.

<?php namespace App; use Illuminate\Database\Eloquent\Model; class Message extends Model { protected $guarded = ['id']; protected $dates = [ 'delivered_at', 'failed_at', ]; public function sender() { return $this->morphTo(); } }

Next, there is a great little tip/trick that you can do to help keep your database records for this sender_type column clean. Currently, if you create a record in your database using this relationship you’ll end up with a DB record that looks like this.

| id | message_id | sender_type | sender_id | delivered_at | failed_at | opened | clicked | created_at | updated_at | |----|--------------------------------------------------|-------------|-----------|--------------|-----------|--------|---------|---------------------|---------------------| | 1 | [email protected] | App\User | 1 | | | | | 2019-09-08 16:52:07 | 2019-09-08 16:52:07 |

You can see that the sender_type is the namespace of your User model. This is fine if you never change the namespace of your model, to clean this up, Laravel has a great Relation::morphMap([...]); method to help make the above a little more readable and a lot less coupled to our namespace.

open your AppServiceProvider.php file and in the register() method add the following.

<?php namespace App\Providers; use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { /** * Register any application services. * * @return void */ public function register() { Relation::morphMap([ 'users' => 'App\User', ]); } /** * Bootstrap any application services. * * @return void */ public function boot() { // } }

By doing the above, when you create a message now, your database record will look like this.

| id | message_id | sender_type | sender_id | delivered_at | failed_at | opened | clicked | created_at | updated_at | |----|--------------------------------------------------|-------------|-----------|--------------|-----------|--------|---------|---------------------|---------------------| | 1 | [email protected] | users | 1 | | | | | 2019-09-08 16:52:07 | 2019-09-08 16:52:07 |

Isn’t that SO MUCH better? If you want to read more about this, check out this post by Joseph Silber.

Set up our Mailgun webhook controller

All right, we’re ready to move on now. Now let’s make a controller for the webhooks. For this I like to namespace things to keep them organized. In your console enter the following.

$ php artisan make:controller Webhooks/MailgunWebhookController

Next, let’s make a middleware for this controller.

$ php artisan make:middleware MailgunWebhookMiddleware

ext, in your routes folder let’s make a webhooks.php file. In here we’re going to add all our webhook routes. I do this to keep things organized and because we want this URL to not have any of the web middleware stack. I also don’t want to have my webhook URL for Mailgun to be prefixed by /api otherwise you could put it in there as well.

In your newly created webhooks.php file add the following route.

Route::group(['namespace' => 'Webhooks',], function () { Route::post('mailgun', 'MailgunWebhookController'); });

If you were to run php artisan route:list you’ll notice the webhook URL you just added doesn’t come up, we’ll need to update our RouteServiceProvider.php to include the routes in this file. So let’s open that up. In the map() function let’s add a method called mapWebhookRoutes()

<?php namespace App\Providers; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; use Illuminate\Support\Facades\Route; class RouteServiceProvider extends ServiceProvider { /** * This namespace is applied to your controller routes. * * In addition, it is set as the URL generator's root namespace. * * @var string */ protected $namespace = 'App\Http\Controllers'; /** * Define your route model bindings, pattern filters, etc. * * @return void */ public function boot() { // parent::boot(); } /** * Define the routes for the application. * * @return void */ public function map() { $this->mapApiRoutes(); $this->mapWebRoutes(); $this->mapWebhookRoutes(); } /** * Define the "web" routes for the application. * * These routes all receive session state, CSRF protection, etc. * * @return void */ protected function mapWebRoutes() { Route::middleware('web') ->namespace($this->namespace) ->group(base_path('routes/web.php')); } /** * Define the "api" routes for the application. * * These routes are typically stateless. * * @return void */ protected function mapApiRoutes() { Route::prefix('api') ->middleware('api') ->namespace($this->namespace) ->group(base_path('routes/api.php')); } /** * Define the "webhook" routes for the application. * * These routes are typically stateless. * * @return void */ protected function mapWebhookRoutes() { Route::prefix('webhooks') ->namespace($this->namespace) ->group(base_path('routes/webhooks.php')); } }

Okay now that we have our route, let’s get our controller set up. Open your MailgunWebhookController.php and let’s add an __invoke() method.

<?php namespace App\Http\Controllers\Webhooks; use App\Http\Controllers\Controller; class MailgunWebhookController extends Controller { public function __invoke() { // we'll handel our inbound webhook here. } }

Once you’ve done that let’s test to see if everything is working. if you run php artisan route:list you should see an output like this.

+--------+----------+----------------------------+------------------+------------------------------------------------------------------------+-------------------------------------------------+ | Domain | Method | URI | Name | Action | Middleware | +--------+----------+----------------------------+------------------+------------------------------------------------------------------------+-------------------------------------------------+ | | GET|HEAD | / | | Closure | web | | | POST | _ignition/execute-solution | | Facade\Ignition\Http\Controllers\ExecuteSolutionController | Facade\Ignition\Http\Middleware\IgnitionEnabled | | | GET|HEAD | _ignition/health-check | | Facade\Ignition\Http\Controllers\HealthCheckController | Facade\Ignition\Http\Middleware\IgnitionEnabled | | | GET|HEAD | _ignition/scripts/{script} | | Facade\Ignition\Http\Controllers\ScriptController | Facade\Ignition\Http\Middleware\IgnitionEnabled | | | POST | _ignition/share-report | | Facade\Ignition\Http\Controllers\ShareReportController | Facade\Ignition\Http\Middleware\IgnitionEnabled | | | GET|HEAD | _ignition/styles/{style} | | Facade\Ignition\Http\Controllers\StyleController | Facade\Ignition\Http\Middleware\IgnitionEnabled | | | GET|HEAD | api/user | | Closure | api,auth:api | | | GET|HEAD | home | home | App\Http\Controllers\[email protected] | web,auth | | | GET|HEAD | login | login | App\Http\Controllers\Auth\[email protected] | web,guest | | | POST | login | | App\Http\Controllers\Auth\[email protected] | web,guest | | | POST | logout | logout | App\Http\Controllers\Auth\[email protected] | web | | | POST | password/email | password.email | App\Http\Controllers\Auth\[email protected] | web,guest | | | GET|HEAD | password/reset | password.request | App\Http\Controllers\Auth\[email protected] | web,guest | | | POST | password/reset | password.update | App\Http\Controllers\Auth\[email protected] | web,guest | | | GET|HEAD | password/reset/{token} | password.reset | App\Http\Controllers\Auth\[email protected] | web,guest | | | POST | register | | App\Http\Controllers\Auth\[email protected] | web,guest | | | GET|HEAD | register | register | App\Http\Controllers\Auth\[email protected] | web,guest | | | POST | webhooks/mailgun | | App\Http\Controllers\Webhooks\MailgunWebhookController | | +--------+----------+----------------------------+------------------+------------------------------------------------------------------------+-------------------------------------------------+

Tip if you want to filter the results to a particular route pattern you can run this php artisan route:list --path=webhooks which will return any routes that match that pattern.

+--------+--------+------------------+------+--------------------------------------------------------+------------+ | Domain | Method | URI | Name | Action | Middleware | +--------+--------+------------------+------+--------------------------------------------------------+------------+ | | POST | webhooks/mailgun | | App\Http\Controllers\Webhooks\MailgunWebhookController | | +--------+--------+------------------+------+--------------------------------------------------------+------------+

Okay, If you got that then you’re golden. Next step let’s get an email that we can send. Let’s make a welcome email for users that register for our app. Since Laravel makes it dead simple to add auth login screens. In your console let’s run these commands. The first will create the auth boilerplate for us which will give us the login and registration pages in our app. Next, we will make a Mailable that we can send our users when they register, finally a listener where we will send that welcome email from.

The Welcome email

$ php artisan make:auth $ php artisan make:mail WelcomeEmail --markdown=emails.welcome $ php artisan make:listener SendWelcomeEmail

Once you’ve run that and you visit your app in the browser, you’ll notice that you now have a login and register nav item. Pretty awesome! Okay now open your EventServiceProvider.php file, in there you’ll notice that there are already events mapped out. let’s add our handler to that Registered event.

<?php namespace App\Providers; use App\Listeners\SendWelcomeEmail; use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Listeners\SendEmailVerificationNotification; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use Illuminate\Support\Facades\Event; class EventServiceProvider extends ServiceProvider { /** * The event listener mappings for the application. * * @var array */ protected $listen = [ Registered::class => [ SendWelcomeEmail::class, SendEmailVerificationNotification::class, ], ]; /** * Register any events for your application. * * @return void */ public function boot() { parent::boot(); // } }

Okay now that you have that done, every time someone registers in your app, this handler will be triggered. If you’re wondering what the SendEmailVerificationNotification class does, it’s used to send the verification email if you choose to require that in your application. You can learn more about that from here. let’s start sending emails to welcome our users! in the SendWelcomeEmail class add the following.

<?php namespace App\Listeners; use App\Mail\WelcomeEmail; use Illuminate\Auth\Events\Registered; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Support\Facades\Mail; class SendWelcomeEmail { /** * Handle the event. * * @param object $event * @return void */ public function handle(Registered $event) { Mail::to($event->user)->send(new WelcomeEmail($event->user)); } }

So now, if you go ahead and register you should get an email sent to you that looks like this.

Welcome email example

This is the default markdown email that ships with Laravel if you see this, things are good. Now that we’re here, let’s start tracking the and saving the outbound messages. To do this we’ll open our App/Mail/WelcomeEmail.php

We’re going to want to add the following.

<?php namespace App\Mail; use App\User; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; use Illuminate\Queue\SerializesModels; class WelcomeEmail extends Mailable { use Queueable, SerializesModels; /** * Create a new message instance. * * @return void */ public function __construct(User $user) { $this->user = $user; } /** * Build the message. * * @return $this */ public function build() { $this->withSwiftMessage(function ($message) { $this->user->message()->create([ 'message_id' => $message->getId(), ]); }); return $this->markdown('emails.welcome'); } }

In the constructor of this class, we’re going to get passed the User that we’re sending this email to, we will be using this to store the outbound Message. The build() code is where we hook into the SwiftMailer to get the message-id and using the message relationship we create the message record in our database. So let’s try and register again and see what happens. Hopefully, you should see a record created in the messages table linked to the user you just registered? If so let’s move on to the webhooks handling.

Handling the incoming webhook

For this, we’ll need to head back into our Mailgun account. From the dashboard select the sending domain that you configured earlier on and then select the webhooks from the sidebar. I am running Laravel Valet locally so I’m going to run valet share and get a URL for my local app to use.

Configured webhooks in Mailgun

Once you’re done adding the 4 events we will be tracking, and yes there are more if you want to track more you’ll be able to add them with ease. So now that we have this let’s start updating our messages. The first thing we’re going to do is flesh out our middleware for our webhook controller. This middleware will ensure that the inbound HTTP request is in fact from Mailgun by verifying the webhook signature using our API key. Mailgun does refer to this as a webhook secret but if you look, you’ll notice that it’s the same token/string as your API key.

<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Response; use Illuminate\Support\Facades\Log; /** * Validate Mailgun Webhooks * @see https://documentation.mailgun.com/user_manual.html#securing-webhooks */ class MailgunWebhook { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { if (!$request->isMethod('post')) { abort(Response::HTTP_FORBIDDEN, 'Only POST requests are allowed.'); } if ($this->verify($request)) { return $next($request); } abort(Response::HTTP_FORBIDDEN); } /** * Build the signature from POST data * * @see https://documentation.mailgun.com/user_manual.html#securing-webhooks * @param $request The request object * @return string */ private function buildSignature($request) { return hash_hmac( 'sha256', sprintf('%s%s', $request->input('timestamp'), $request->input('token')), config('services.mailgun.secret') ); } public function verify($request) { $token = $request->input('signature.token'); $timestamp = $request->input('signature.timestamp'); $signature = $request->input('signature.signature'); if (abs(time() - $timestamp) > 15) { return false; } return hash_hmac('sha256', $timestamp . $token, config('services.mailgun.secret')) === $signature; } }

In the above, we take the inbound request and analyze it to ensure it’s authentic before allowing our controller to handle it. You can read more in the Mailgun docs. Below is an example of the payload you can expect from this webhook.

{ “signature”: { "timestamp": "1529006854", "token": "a8ce0edb2dd8301dee6c2405235584e45aa91d1e9f979f3de0", "signature": "d2271d12299f6592d9d44cd9d250f0704e4674c30d79d07c47a66f95ce71cf55" } “event-data”: { "event": "opened", "timestamp": 1529006854.329574, "id": "DACSsAdVSeGpLid7TN03WA", // ... }, "message": { "headers": { "message-id": "[email protected]", } } }

In the verify method you can see the three main things we have here, the timestamp, the token and the signature. What we need to do is concatenate the timestamp and token values, encode the resulting string with the HMAC algorithm (using your API Key as a key and SHA256 digest mode). Then we compare the result to the signature we in get the webhook payload. We also check if the timestamp is not too old which would make it stale. If everything looks good from here, we let this request through and we handle it in our controller.

<?php namespace App\Http\Controllers\Webhooks; use App\Http\Controllers\Controller; use App\Http\Middleware\Webhooks\MailgunWebhook; use App\Message; use Illuminate\Http\Request; class MailgunWebhookController extends Controller { /** * Constructor. */ public function __construct() { $this->middleware(MailgunWebhook::class); } /** * Handles the Stripe Webhook call. * * @param \Illuminate\Http\Request $request * * @return mixed */ public function __invoke(Request $request) { $data = $request->get('event-data'); $message_id = $data['message']['headers']['message-id']; if ($email = Message::whereMessageId($message_id)->first()) { if ($data['event'] === 'opened' || $data['event'] === 'clicked') { $email->increment($data['event']); } if ($data['event'] === 'delivered' || $data['event'] === 'failed') { $email->update(["{$data['event']}_at" => now()]); } } } }

In the construct method, we define the middleware we want to run to protect this controller, and in the __invoke() method we wrote before we will extract the data we need from the request. The message-id that we set in our messages table is found in message -> headers -> message-id this is id to retrieve the message record we want to update. Next we can find the event type (ie, clicked, opened, delivered, failed) in the event-data -> event, so now all we need to do is add some logic for opened or clicked events we’re going to increment a column, otherwise its a delivered or failed message so we’ll update those timestamps to the current UTC of the event. There you have it.

And there you have it, from here you can add messages to other models in your app, say invoices, or any other event that triggers an email and track if your user got it, opened it or clicked etc.

I hope you have found this useful! If you have any comments or things you think can improve this, send me a message or Tweet or something.. Bonus points if it’s a Drake gif.

Drizzy

via Laravel News Links
Track successful email deliveries, clicks and opens in your Laravel PHP application with Mailgun

Concealed Carry Practice: Where Should You Sit in This Photo and Why?

In an ever-increasingly violent world, situational awareness becomes your most important defense against attack. And it’s in your everyday concealed carry practice where the seemingly mundane decisions can have the most effect. Take a different route. Clear away the thick bushes by the window. And in public, put more thought into where you sit.

Much more than a handgun goes into completing a good Concealed Carry Rig.

RELATED STORY

Everything to Consider When Setting Up a Concealed Carry Rig

Take this photo from the U.S. Concealed Carry Association (USCCA) for example. Walk into a busy, crowded coffee shop or similar public setting, and there are a million little choices that impact are safety. But maybe no choice is quite as interesting as where one sits.

Concealed Carry Practice

Any student of the gun and certainly the old West knows the famous tales of Bill Hickok. Preoccupied with his poker hand, Hickok, for the reported first and only time, sat with his back to the door in Nuttal & Mann’s #10 Saloon in Deadwood, South Dakota. That’s when his assailant, forever known as “The Coward” Jack McCall, took advantage and quietly slipped in the door and executed Wild Bill in the back of the head. For our USCCA map, that would make most of the “3” seats pretty undesirable.

Since trouble is most likely to come through the front door, seating choice should reflect the location of the emergency exit along the opposite wall. The two bathrooms are close by, which could potentially offer refuge as well, and possibly a window as well. Table “A” gives quick access to both bathrooms, while tables “B” and “C” provide even quicker exits.

Table “I,” seats “1” and “4” offer the most tactical advantage. With the wall at one’s back, patrons have the most open field of view of the entire coffee shop. You can see both exits, and even the bathrooms. From table “I,” you can also see every other single customer, worker, etc. You’ve got it wired.

There’s a lot of choices to be made in everyday life. Giving them some thought, and thinking of your own capabilities and needs, provides the beginning of a plan. And it’s planning and being prepared that often help provide our best defense.

The post Concealed Carry Practice: Where Should You Sit in This Photo and Why? appeared first on Personal Defense World.

via Personal Defense World
Concealed Carry Practice: Where Should You Sit in This Photo and Why?

Clubhouse announces new collaboration tool and free version of its project management platform

Clubhouse — the software project management platform focused on team collaboration, workflow transparency and ease of integration — is taking another big step towards its goal of democratizing efficient software development.

Traditionally, legacy project management programs in software development can often appear like an engineer feeding frenzy around a clunky stack of to-dos. Engineers have limited clarity into the work being done by other members of their team or into project tasks that fall outside of their own silo.

Clubhouse has long been focused on easing the headaches of software development workflows by providing full visibility into the status of specific tasks, the work being done by all team members across a project, as well as higher-level project plans and goals. Clubhouse also offers easy integration with other development tools as well as its own API to better support the cross-functionality a new user may want.

Today, Clubhouse released a free version of its project management platform, that offers teams of up to 10 people unlimited access to the product’s full suite of features, as well as unlimited app integrations.

The company also announced it will be launching an engineer focused collaboration and documentation tool later this year, that will be fully integrated with the Clubhouse project management product. The new product dubbed “Clubhouse Write” is currently in beta, but will allow development teams to collaborate, organize and comment on project documentation in real-time, enabling further inter-team communication and a more open workflow.

The broader mission behind the Clubhouse Write tool and the core product’s free plan is to support more key functions in the development process for more people, ultimately making it easier for anyone to start dynamic and distributed software teams and ideate on projects.

write screenshot

“Clubhouse Write” Beta Version. Image via Clubhouse

In an interview with TechCrunch, Clubhouse also discussed how the offerings will provide key competitive positioning against larger incumbents in the software project management space. Clubhouse has long competed with Atlassian’s project management tool “Jira”, but now the company is doubling down by launching Clubhouse Write which will compete head-on with Atlassian’s team collaboration product Confluence.

According to recent Atlassian investor presentations, Jira and Confluence make up the lion’s share of the Atlassian’s business and revenues. And with Atlassian’s market capitalization of ~$30 billion, Clubhouse has its sights set on what it views as a significant market share opportunity.

According to Clubhouse, the company believes it’s in pole position to capture a serious chunk of Atlassian’s foothold given it designed its two products to have tighter integration than the legacy platforms, and since Clubhouse is essentially providing free versions of what many are already paying for to date.

And while Atlassian is far from the only competitor in the cluttered project management space, few if any competing platforms are offering a full project tool kit for free, according to the company. Clubhouse is also encouraged by the strong support it has received from the engineering community to date. In a previous interview with TechCrunch’s Danny Crichton, the company told TechCrunch it had reached at least 700 enterprise customers using the platform before hiring any sales reps, and users of the platform already include Nubank, Dataiku, and Atrium amongst thousands of others.

Clubhouse has ambitious plans to further expand its footprint, having raised $16 million to date through its Series A according to Crunchbase, with investments from a long list of Silicon Valley mainstays including Battery Ventures, Resolute Ventures, Lerer Hippeau, RRE Ventures, BoxGroup, and others.

A former CTO himself, Clubhouse cofounder and CEO Kurt Schrader is intimately familiar with the opacity in product development that frustrates engineers and complicates release schedules. Schrader and Clubhouse CMO Mitch Wainer believe Clubhouse can maintain its organic growth by that staying hyperfocused on designing for project managers and creating simple workflows that keep engineers happy. According to Schrader, the company ultimately wants to be the “default [destination] for modern software teams to plan and build software.”

“Clubhouse is the best software project management app in the world,” he said. “We want all teams to have access to a world-class tool from day one whether it’s a 5 or 5,000 person team.”


via TechCrunch
Clubhouse announces new collaboration tool and free version of its project management platform