Contracts

Contracts are scary business. Usually a business’s default contract is strongly favorable to them and it can be hard on the relationship to push for tons of changes. I was sent a subcontractor contract today that just made no sense, so I sent back my default contract instead along with a big list of changes they would have to rewrite into their contract if they wished to work with me. Just a lot of annoyance.

Delice De Bourgogne

I really like this cheese named Delice De Bourgogne. As a matter of fact, I am sitting at my desk at work eating a block of it with a knife. Very good stuff. Very filling. Lots of calories to power my brain at work.

Basecamp 3 API PHP Connection Class

Below is the connection class I have written for pulling Basecamp 3 API data using PHP. This is a Laravel class, but you can use the code in any PHP app with some tweaks. I am still working on it, but this is what I have so far. I have implemented caching using ETag, but not Last-Modified yet. It also implements pagination.

<?php namespace App\Services;

use App\BasecampTokens;
use App\BcProject;
use App\BcResponse;
use App\BcTodolist;
use League\OAuth2\Client\Provider\GenericProvider;
use GuzzleHttp\Client;
use League\OAuth2\Client\Token\AccessToken;
use Redirect;
use Session;
use DB;

class BasecampService
{
    var $provider = null;
    var $response = null;
    var $uri = null;
    var $responseHeaders = null;
    var $allObjects;
    var $httpCache = [];
    var $firstTime = true;
    var $baseUri = null;
    var $userAgent;

    public function __construct()
    {
        $this->provider = new GenericProvider([
            'clientId' => env('basecamp_client_id'),    // The client ID assigned to you by the provider
            'clientSecret' => env('basecamp_client_secret'),   // The client password assigned to you by the provider
            'redirectUri' => url('basecamp/getToken'),
            'urlAuthorize' => 'https://launchpad.37signals.com/authorization/new',
            'urlAccessToken' => 'https://launchpad.37signals.com/authorization/token',
            'urlResourceOwnerDetails' => 'https://github.com/basecamp/api/blob/master/sections/authentication.md'
        ]);
        $this->baseUri = 'https://3.basecampapi.com/XXXXXX/';
        $this->userAgent = 'j.com (joel@XXXXX.com)';
    }

    public function parseResponse()
    {
        $this->responseBody = json_decode($this->response->getBody());
        $this->responseHeaders = $this->response->getHeaders();

        if (isset($this->responseHeaders['X-Total-Count'])) {
            $this->responseTotalCount = $this->responseHeaders['X-Total-Count'][0];
        }

        if (isset($this->responseHeaders['Link'])) {
            $this->responseLink = $this->responseHeaders['Link'][0];
        } else {
            $this->responseLink = null;
        }

        if (isset($this->responseHeaders['X-Ratelimit'])) {
            $this->responseRateLimit = $this->responseHeaders['X-Ratelimit'][0];
        }

        if (isset($this->responseHeaders['ETag'])) {
            $this->responseEtag = $this->responseHeaders['ETag'][0];
        }
    }

    public function initiateBasecampConnection()
    {
        $this->_client = new Client([
            'base_uri' => $this->baseUri
        ]);

        $headers = ['headers' => [
            'User-Agent' => $this->userAgent,
            "Authorization" => "Bearer " . $this->getAccessToken(),
        ]];
        if ($this->firstTime == true) {
            $bcResponse = BcResponse::where('link', $this->uri)->first();
        } else {
            $bcResponse = BcResponse::where('link', $this->responseLink)->first();
        }
        if ($bcResponse instanceof BcResponse) {
            $eTag = $bcResponse->etag;
        } else {
            $eTag = null;
        }

        $LastModified = null;

        $httpCache = [
            'If-None-Match' => $eTag, //ETag
            'If-Modified-Since' => $LastModified //Last-Modified
        ];

        if ($httpCache['If-None-Match'] != null) {
            $headers['headers']['If-None-Match'] = $httpCache['If-None-Match'];
        }
        if ($httpCache['If-Modified-Since'] != null) {
            $headers['headers']['If-Modified-Since'] = $httpCache['If-Modified-Since'];
        }

        try {
            $this->response = $this->_client->request(
                'GET',
                $this->uri,
                $headers
            );

        } catch (ClientException $e) {
            dd($e);
        }
    }

    public function getAllRecords()
    {
        $this->allObjects = [];
        do {
            $this->initiateBasecampConnection();
            $this->parseResponse();
            $this->saveResponse();
            if ($this->response->getStatusCode() == 304) {
                //This is for caching
            } else {
                if ($this->response->getStatusCode() == 429) {
                    sleep(10);
                    $this->initiateBasecampConnection();
                    $this->parseResponse();
                }
                $this->allObjects = array_merge($this->allObjects, $this->responseBody);
            }
            if (isset($this->responseLink)) {
                $regex = '#<(.*?)>#';
                preg_match($regex, $this->responseLink, $matches);
                $this->uri = $matches[1];
            }
        } while (isset($this->responseLink));
    }

    public function saveResponse()
    {
        $bcResponse = BcResponse::where('link', $this->uri)->first();
        if (!$bcResponse instanceof BcResponse) {
            $bcResponse = new BcResponse();
        }
        $bcResponse->link = $this->uri;
        $bcResponse->etag = $this->responseEtag;
        $bcResponse->save();
    }

    public function getTodolists()
    {
        $allBasecampProjects = BcProject::all();
        foreach($allBasecampProjects as $bcProject){
            $todoSetId = $bcProject->todoset_id;
            $bucketId = $bcProject->bc_id;
            $this->uri = 'https://3.basecampapi.com/3140673/buckets/'.$bucketId.'/todosets/'.$todoSetId.'/todolists.json';
            $this->getAllRecords();
            foreach($this->allObjects as $todolist){
                $bcTodolist = BcTodolist::where('bc_todolist_id', $todolist->id)->first();
                if (!$bcTodolist instanceof BcTodolist) {
                    $bcTodolist = new BcTodolist();
                }
                $bcTodolist->bc_todolist_id = $todolist->id;
                $bcTodolist->bc_status = $todolist->status;
                $bcTodolist->bc_name = $todolist->name;
                $bcTodolist->bc_description = $todolist->description;
                $bcTodolist->bc_todoset_id = $todolist->parent->id;
                $bcTodolist->bc_project_id = $todolist->bucket->id;
                $bcTodolist->bc_app_url = $todolist->app_url;
                $bcTodolist->bc_url = $todolist->url;
                $bcTodolist->bc_completed_ratio = $todolist->completed_ratio;
                $bcTodolist->bc_completed = $todolist->completed;
                $bcTodolist->save();
            }
        }
        dd($this->allObjects);
    }

    public function getBasecamps()
    {

//        dd($eTag);
//        $eTag = 'W/"3fc3c26a37d4f474bb120d6164dcadbd"';
        $this->uri = 'https://3.basecampapi.com/3140673/projects.json';
        $this->getAllRecords();

        foreach ($this->allObjects as $bcProject) {
            $dbBcproject = BcProject::where('bc_id', $bcProject->id)->first();
            if (!$dbBcproject instanceof BcProject) {
                $dbBcproject = new BcProject();
            }
            $dbBcproject->bc_id = $bcProject->id;
            $dbBcproject->bc_status = $bcProject->status;
            $dbBcproject->bc_name = $bcProject->name;
            $dbBcproject->bc_description = $bcProject->description;
            $dbBcproject->bc_purpose = $bcProject->purpose;
            $dbBcproject->bc_bookmark_url = $bcProject->bookmark_url;
            $dbBcproject->bc_url = $bcProject->url;
            $dbBcproject->bc_app_url = $bcProject->app_url;
            $dbBcproject->todoset_id = $bcProject->dock[2]->id;
            $dbBcproject->save();
        }
        dd($this->allObjects);

    }

    public function getAccessToken()
    {
        $basecamp_token = DB::table('basecamp_tokens')
            ->orderBy('id', 'desc')->first();

        $basecamp_token = json_decode(json_encode($basecamp_token), true);

        $existingAccessToken = new AccessToken($basecamp_token);

        if ($existingAccessToken->hasExpired()) {
            $newAccessToken = $this->provider->getAccessToken('refresh_token', [
                'refresh_token' => $existingAccessToken->getRefreshToken()
            ]);
            // Purge old access token and store new access token to your data store.
            $basecampToken = new BasecampTokens();
            $basecampToken->access_token = $newAccessToken->getToken();
            $basecampToken->refresh_token = $newAccessToken->getRefreshToken();
            $basecampToken->expires = $newAccessToken->getExpires();
            $basecampToken->save();

            $existingAccessToken = $newAccessToken;

            Session::flash('message', 'Got token from basecamp');
        }


        return $existingAccessToken;
    }

    public
    function getToken()
    {
// If we don't have an authorization code then get one
        if (!isset($_GET['code'])) {

            // Fetch the authorization URL from the provider; this returns the
            // urlAuthorize option and generates and applies any necessary parameters
            // (e.g. state).
            $options = ['type' => 'web_server'];

            $authorizationUrl = $this->provider->getAuthorizationUrl($options);

            // Get the state generated for you and store it to the session.
            $_SESSION['oauth2state'] = $this->provider->getState();

            // Redirect the user to the authorization URL.
            header('Location: ' . $authorizationUrl);
            exit;

// Check given state against previously stored one to mitigate CSRF attack
//        } elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
//
//            unset($_SESSION['oauth2state']);
//            exit('Invalid state');

        } else {

            try {

                // Try to get an access token using the authorization code grant.
                $accessToken = $this->provider->getAccessToken('authorization_code', [
                    'code' => $_GET['code'],
                    'type' => 'web_server'
                ]);

                // We have an access token, which we may use in authenticated
                // requests against the service provider's API.
                $basecampToken = new BasecampTokens();
//                echo $accessToken->getToken() . "\n";
                $basecampToken->access_token = $accessToken->getToken();
//                echo $accessToken->getRefreshToken() . "\n";
                $basecampToken->refresh_token = $accessToken->getRefreshToken();
//                echo $accessToken->getExpires() . "\n";
                $basecampToken->expires = $accessToken->getExpires();
//                echo ($accessToken->hasExpired() ? 'expired' : 'not expired') . "\n";
                $basecampToken->save();

                Session::flash('message', 'Got token from basecamp');

                // Using the access token, we may look up details about the
                // resource owner.
//                $resourceOwner = $this->provider->getResourceOwner($accessToken);
//
//                var_export($resourceOwner->toArray());
//
//                // The provider provides a way to get an authenticated API request for
//                // the service, using the access token; it returns an object conforming
//                // to Psr\Http\Message\RequestInterface.
//                $request = $this->provider->getAuthenticatedRequest(
//                    'GET',
//                    'http://brentertainment.com/oauth2/lockdin/resource',
//                    $accessToken
//                );

            } catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {

                // Failed to get the access token or user details.
                exit($e->getMessage());
                Session::flash('message', $e->getMessage());

            }
        }
    }


}

Secret to Happiness

I try to track my happiness along with a number of variables every single day in my spreadsheet. So far, I have found a few things that can dramatically improve my daily mood:

  1. Most important is social contact. I have a habit now of calling either a family member or friend both on my way to work and my way home. I also try to schedule activities that will get me to engage and talk to others.
  2. Writing down what I am grateful for every day in my log really has helped my happiness. It keeps my perspective if things are annoying to me that I have a huge amount that I should be very grateful for – a wonderful wife, comfortable and safe home, great colleagues, etc.

Trump Election

Here is my Facebook post from yesterday after Trump’s election:

What are the actual results of Trumps win last night?

Pros and con’s of Trump and the Republicans winning out as best I can see them. These are all just guesses as Trump is extremely unpredictable and never laid out firm plans.

Cons – climate change won’t be addressed, globalization of trade will face setbacks, minority and womens rights will not advance, an ugly bigot will be Americas leader, the risk of war is far higher since Trump can’t control his anger

Pros – tax simplification reform might actually happen, business regulations might be simplified, libertarian leaning supreme court justices

Not sure – aca reform

Remember – Trump is not a true Republican… Many of his ideas run in the big government direction and I think we will be surprised by some of policies.

My predicted result – big parts of the Republican agenda will be implemented in the next two years, then the Democrats will take back Congress and no further changes will happen during Trumps presidency.

What do you think?

Depression / Gratitude

Science shows that writing down what your grateful for can help combat depression and increase happiness. It has worked for me for sure! Every day I write down one thing I am grateful for. See my list recently:

Relaxing honeymoon day
Happy life
Good honeymoon
Visiting grandparents
My family
Jimmy running with laurel and i, grandpa and bestemor
Healthy life with laurel
Nice family visit
Able to work from home when sick
Laurel is great
Things to do
Its the weekend
Laurel is best wifey ever
Back in the office
Freedom to structure my days
Great CT team right now
Sleep
Healthy lifestyle
Going to portland on wednesday
Back in my routine tomorrow
Home sweet home
Top ranked for everything
Laurel is an awesome wife
Not in debt
Security
I have a good brain
Healthy diet
Very happy, perfect life now
Choose my own schedule
I am proud of what i do
No hangover, i feel great
Lots of free time
Flexible schedule
Fun man date with jeremy
Laurel being reasonable and open to change
Visiting grandma gross this weekend
Back home
Driving purpose to my life
Great friends like Victoria and Jeremy
Laurels family here in LA
Laurel great wife
Romy is actually solid
No lawsuits of big size going on
I drink less usually nowadays
Generally good health
Business is super good
treadmill desk
Kickoff call a day this week
Fun rock climbing with scott b yesterday
Renewed motivation this time of year
I am not crazy like everyone else lol
Fun weekend with friends (jeremy bbq, scott’s gf mel bday)
More social contact lately due to my regular morning and evening calls
Productive week
Fun purposes to work on
Bishop trip this weekend
Excited about bishop in 2 days
Done with work for the week!
Great rock climbing weekend
Justin visited last night
Treadmill desk keeps me moving
2 hours climbing mid day with Matt & max
Highly productive weekend
Jordan is best friend & best brother
Business is relatively smooth
Rock climbing during work day
Laurel rocks
Perfect schedule
laurel rock climbed last night
I always have enough to eat
I have shelter

Crushing Grip

I should be a politician as rock climbing has really helped strengthen my handshake grip. I can impress all the yokels with a firm, manly handshake.

That’s the extent of my claim to fame.

Todo management application

I have been building an internal todo management application for Coalition. The key difference between my todo application and others out there is that it has automatically sortable priority lists by client and by user that synchronizes across multiple users and multiple clients. I am also tracking a user activity log and going to sync it with Basecamp. Hopefully it helps my team focus on their top priorities better.