Jay Paulynice

My thoughts about life, startups, and weight lifting.

Page 2 of 2

REST Clone Operation

Super excited for what we have coming in 2023!

With cloud native, one of the major challenges is observability and being able to debug production issues quickly. But you can’t really debug your app while it’s being used by users in real time.

It’s nice to have a temporary environment that is as close to production as possible, but spinning up and tearing down a cloud infrastructure quickly is a major pain for developers.

One of the cool features we just shipped at Oatfin is the ability to clone an infrastructure or environment one-to-one to make it easy to reproduce and troubleshoot production problems.

Oatfin UI showing the clone operation

In solving this problem, it would have been nice to have a COPY or CLONE operation in REST. Something I think is fundamental to almost every API.

For now I’m doing:

POST /resource?source=id

If the “source” parameter is present as a query parameter, then it’s a clone operation, otherwise it’s a create operation. The operation is not idempotent meaning it will create a new copy each time the API is called.

Here is what this looks like. For the frontend, I’m using React, Typescript with a tool called umijs and antdesign from Ant financial:

Create request

import { request } from 'umi';

export async function createApp(params) {
  return request('/v1/apps', {
    method: 'POST',
    data: params,
    headers: {
      Authorization: 'Bearer ACCESS_TOKEN,
    },
  })
}

Clone request

import { request } from 'umi';

export async function cloneApp(params, id) {
  return request(`/v1/apps?source=${id}`, {
    method: 'POST',
    data: params,
    headers: {
      Authorization: 'Bearer ACCESS_TOKEN,
    },
  })
}

Calling these functions inside the React component:

export const AppComponent: FC<Props> = (props) => {
    ...
    const handleCreate = async (values) => {
        const res = await createApp(values);
        ...
    };

    const handleClone = async (values) => {
        const res = await cloneApp(values);
        ...
    };

    return (
        <>
            <CreateModal
                ...
                onSubmit={handleCreate}
            />
            <CloneModal
                ...
                onSubmit={handleClone}
            />
        </>
  )
}

The Python & Flask code skeleton:

@api.route('/apps', methods=['POST'])
@jwt_required()
def create_clone_app():

    req_data = flask.request.get_json()
    clone_source = flask.request.args.get('source')

    if not clone_source:
        app = AppsService().create(...)
    else:
        app = AppsService().clone(...)

    return flask.jsonify(
        app=app.json(), 
        message='success',
    ), 200

I’d be interested to see how others deal with the CLONE or COPY in REST.

New Year Goals

Lots of great things happened in 2022, but it was no doubt one of the most brutal year given the recession, mass layoffs, and investor pull back. I learned a lot about fundraising, business, and technology.

Where I lacked a lot was building relationships with investors. Further complicating things was the Covid pandemic. Building a company when there is a raging global pandemic was challenging.

Some of my focus areas for 2023:

Build relationships

  • Customers
  • Strategic Partners
  • Employees/Co-founders
  • Investors

Expand my knowledge about Venture Capital/Financing

  • Grants like the NSF SBIR/STTR program
  • SAFEs
  • Angel Groups
  • Accelerators
  • Convertible Notes
  • Crowdfunding

Expand my knowledge about business

  • Startup business valuation
  • Intellectual property
  • Financial Accounting
  • Pitch deck/Business plan
  • Selling and marketing to enterprise businesses

Expand my knowledge about Product Management and Technology

  • Product Management tools (JIRA, Confluence, etc.)
  • Cloud certifications (AWS, Google Cloud, Azure)
  • Learn a new programming language like Rust or Go
  • Developer conferences

23andMe Ancestry

Today, I got the complete result of my 23andMe Ancestry DNA test. I was born in Haiti, but moved to the US as a kid. I knew my ancestors mostly came from West Africa, but the exact place is something I always wanted to know. On my mom’s side, I knew I also had some European heritage but not much information.

The most interesting fact is my ancestry timeline. The timeline shows the ancestors who were 100% heritage and the time they were likely born. It seems correct because Haiti got its independence in 1804 which puts some of my ancestors as European slave owners from Spain, Portugal, Italy as well as Indigenous Americans and Southern East Africans in the times of slavery.

It’s also very interesting that I have a 100% Nigerian ancestor who was born as recently as the 1900s, which leaves a lot for research. To my knowledge, none of my grandparents is Nigerian. It could be a great-grandparent that I don’t know have much information about.

Some surprises, but not really that surprised. In college, I met a lot of friends from Africa who instantly recognized me as a fellow African, but I was somewhat annoyed because I had never set foot in Africa. Growing up, to tell Haitians that they are Africans is somewhat derogatory. It’s a form of self-hatred and ignorance. That changed for me because I was exposed to a lot of Africans in college and grad school.

I got along well and felt a connection with Nigerians, Ghanaians, Liberians, Senegalese, Tanzanians, Rwandans etc because I saw myself in them. In fact some of my best friends from are from this area.

The results:

95% Sub-Saharan African

  • 46.3% Nigerian
  • 23% Ghanaian, Liberian, Sierra Leone
  • 11.1% Angolan, Congolese
  • 7% Broadly West African
  • 6.3% Senegambian (Senegal/Gambia), Guinean
  • 1.3% South East Africa

4% European

  • 2.4% Spanish, Portuguese
  • 0.8% Italian
  • 1% Broad European

1% Trace/Unassigned ancestry

Sub-Saharan Africa

Congo and Southern East Africa

Some of my European ancestors are from Spain, Europe, Italy

The 23andMe data set is very small (12 million customers compared to 8 billion people) so it’s interesting to see that I have 1380 distant relatives (2nd, 3rd, 4th, and 5th Cousins) throughout the world of which 477 have added a location on the map.

Sadly, not many distant relatives show up in Africa and the far East yet.

Empty Offices

I was talking to a friend last week and one of the topic that came up was empty offices. You know the massive skyscrapers that were built to anticipate the growth of corporate workers.

But with the lingering effect of the pandemic, these beautiful massive buildings we all call “downtown” are going to be largely abandoned. I think the story will end just like how manufacturing companies disappeared in the early 80s and 90s creating largely abandoned factories and towns.

Currently, few buildings in Boston are 100% occupied. In fact, the average vacancy rate around Boston towns is around 25%. I think this trend will continue as we see more and more layoffs.

In the last 20 years, every company has had to become a tech company except of course certain industries like healthcare, education, and government. With more advances in generative AI and machine learning, we are seeing a rapid decline in creative work like digital marketing, advertising, and of course newspapers. Even fields we thought would not be impacted are currently impacted like software engineering.

Oatfin Selected for Google For Startups Founders Academy

We are super excited to announce that Oatfin will be part of the Google For Startups Founders Academy! At Oatfin, our mission is to enable software engineers to deliver cloud applications faster through self-service automation. We do for software engineers what self-checkout does for retail stores. We enable them to be more agile and remove dependency on platform teams to deliver cloud applications.

The Google For Startup Founders Academy begins on March 3rd and consists of hands-on workshops across a range of topics including customer acquisition, hiring, fundraising, and tech enablement. We are one of 50 high-potential startups that the Google for Startups team selected for the first nationwide cohort. Last year, it was piloted in Atlanta with 45 Georgia based companies.

With Google’s advanced technologies and their sophisticated ecosystem of cutting edge tools, we are excited about how that will help us further our mission. We plan to leverage Google Cloud to further enable developers to deliver cloud applications faster.

Login With Github – Flask/React

While working on Oatfin, one use case we just finished implementing is using Github to allow users to sign up and login for the app. Users can now easily login with Google, Github, and Gitlab.

Our default login is also password-less meaning a user can login with just an email address. We send the login link directly to the email. This adds a layer of security because the user has to have access to the email to login and also validates that the user is a real person. Another problem password-less solves is syncing users from different sign in providers. We can guarantee a user who signed in with Google is the same user from Github or Gitlab.

Our app uses React with Typescript on the front-end and Python, Flask on the back-end. Here is what it looks like.

Part one is to create an OAuth app in Github as explained here.

Part two is setting up the React/Typescript component. When unauthenticated users visit the home page, they are redirected to the login page. That should be your application’s default behavior already.

When users click your ‘Login with Github’ button, they are first sent to Github’s login page with the scopes you want and your client_id like this:


const onClick = async () => {
    window.location.href = 
    'https://github.com/login/oauth/authorize?scope=user:email&client_id=YOUR_CLIENT_ID'
  }

After visiting Github’s website and they login, Github redirects back to your call back page which might still be the login page in our case. In your callback url, there is a now parameter ?code=some_code_text.

Your goal now is to take the code returned from Github and pass it to the Python/Flask app. My login component looks like this:


const Login: React.FC<{}> = () => {
    // ...
  useEffect(() => {
    // see if code was returned, returns an error if the user denies the request
    const newUrl = window.location.href;
    const hasCode = newUrl.includes('?code=');

    if (hasCode) {
      // get the code value
      const url = newUrl.split('?code=')[1].split('#/login');
      const data = {
        code: url[0],
      };
      // send the code to the backend
      submitGithub(data as LoginParamsType);
    }
  }, [submitGithub]);


  const onClick = async () => {
    window.location.href = 
    'https://github.com/login/oauth/authorize?scope=user:email&client_id=YOUR_CLIENT_ID'
  }

    return (
          <Button onClick={onClick} 
            Continue with Github
          </Button>
    )
}

Here we take the code and call the API, which returns access token to store in localStorage.


const submitGithub = async (values: LoginParamsType) => {
    try {
      const res = await accountLogin({ ...values });
      if (res !== undefined && res.access_token !== undefined) {
        window.localStorage.setItem('oatfin_access_token', res.access_token);
    } catch (error) {
      message.error('Unable to login with Github.');
    }
  };

Part three is the Python/Flask login API. We make 2 calls to Github: first to exchange the code we got from the front-end with an access token, then to use the access token to get the user details.


import requests

@api.route('/login', methods=['POST'])
def login():

    req_data = flask.request.get_json()
    code = req_data.get('code')

    if code:
        data = {
                'client_id': app_config.GITHUB_CLIENT_ID,
                'client_secret': app_config.GITHUB_CLIENT_SECRET,
                'code': code
            }
        # exchange the 'code' for an access token
        res = requests.post(
            url='https://github.com/login/oauth/access_token',
            data=data,
            headers={'Accept': 'application/json'}
        )

        if res.status_code != 200:
            raise UnauthenticatedError()

        res_json = res.json()
        access_token = res_json['access_token']

        # get the user details using the access token
        res = requests.get(
            url='https://api.github.com/user',
            headers={
                'Accept': 'application/json',
                'Authorization': 'token {}'.format(access_token)
            }
        )
        if res.status_code != 200:
            raise UnauthenticatedError()

        res_json = res.json()

        names = res_json['name'].split()
        first_name = names[0]
        last_name = names[1]
        login = res_json['login'] or res_json['email']
        avatar = res_json['avatar_url']

        # create the user
        user = UserService().create(...)
        access_token = create_access_token(identity=user.json())

    return flask.jsonify(
        access_token=access_token
    ), 200

Bringing The Blog Back

Just getting my blog back up so I can write about things I find interesting! The last few years have been a period of high growth and learning. One of the best things I did was getting off social media in general and instead spending more time in the gym, reading, meditating and learning. I recently tried to resurrect my LinkedIn profile only because otherwise people don’t think I exist.

Today I finished reading the book, “The Instant Millionaire” which I found very interesting. It’s about a young man who despite several disappointments and setbacks, sets out to become a millionaire. He meets an old gardener who gives him the formula to become an instant millionaire.

I also recently read “The Alchemist”, along the same lines. The book is about a shepherd boy who yearns to travel in search of a worldly treasure as extravagant as any ever found. He meets an Alchemist who guides him on his journey.

There are several key messages in these 2 books and perhaps, the most important one as stated in “The Instant Millionaire”, is:

“When the student is ready, the teacher will appear!”

The opposite is also true. If the student isn’t ready, the teacher will never be able to convince the student. Instead, they will become each other’s nemesis. The student has to set out in search of the light and also be in a state of acceptance! It’s worth saying the teacher could be a 5 year old and the student a 60 year old and vice versa. While the acceptance state can start anytime, it can be influenced by involving the subconscious mind. Once the subconscious mind accepts the message, it’s then easily executed by the conscious mind.

This message rings true in just about every walk of life. It’s also the source of every human struggle as illustrated by the story of Jesus in the Bible. When the teacher shows up before the student is ready, it causes a struggle between logic and emotion whereby emotion always wins!

When I set out to become stronger several years ago, I had no clue what I was doing in the gym, but I was “lifting.” Should someone tell me I was doing something wrong, I would be pissed at them! It’s only recently that I learned to lift properly having picked up the book, “Starting Strength”.

Back to the lessons in these books, the most important message is:

“Financial prosperity and a fulfilling life are goals we can all achieve if we understand the principles of success!”

I just started reading “Choose Yourself!” I’m hoping to write more about these books. Hopefully soon! Cheers!

Newer posts »