Skip to content

Instantly share code, notes, and snippets.

@lipfreitas
Last active January 28, 2021 16:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lipfreitas/94bb1a256d227c9edbff8837bee6a383 to your computer and use it in GitHub Desktop.
Save lipfreitas/94bb1a256d227c9edbff8837bee6a383 to your computer and use it in GitHub Desktop.
CrossKnowledge - Technical challenge (PHP)

Technical challenge

Here is the description of what you'll need to implement on this technical challenge.

Challenges

1. Cache function

Implement a function or class (it can be on the same file - request.php) to cache requests made with the existing code, preventing unecessary calls. You may use this Redis module as a cache service. Feel free to change the code within the existing functions, but do not alter their behaviour.

Context: Caching requests can be useful to avoid unecessary HTTP calls for the same resources, however, the resources can change during time, so it is important to keep in mind that cache needs to be invalidated at some point.

Note: You may use any Python version and import other modules, unless they implement cache services for the requests.

2. Date formatting

Implement a JavaScript code that replaces the value (innerHTML value) from elements with the class js-date-format with the formatted value of the time passed since the element initial time. The value within the elements will be a ISO date format (2019-04-05T12:00:00.000Z for example). It will be tested on Google Chrome.

Use the following format:

  • 1 second ago OR X seconds ago
  • 1 minute ago OR X minutes ago
  • 1 hour ago OR X hours ago
  • Date in ISO format (original format)

Working example:

Working example

Note: You may use ecmascript 6 features but must not use any framework or add any dependency.

3. Apply style

Implement the CSS code to make the component on component.html look like the desired mockup below. Add attributes as you may need, but do not use HTML tags as CSS selectors to implement the styles. It will be tested on Google Chrome.

Mockup:

Desired mockup

Note #1: You should use new CSS features and add classes as you need, but try not to change the HTML structure.

Note #2: We recommend you try using BEM.

Evaluation

This is a list of what will be evaluated in your code:

<?php
class Cache
{
const redisServer = 'localhost';
const redisServerPort = 0101;
private $redis;
public function __construct(){
$this->redis = new Redis();
$this->redis->connect(self::redisServer, self::redisServerPort);
}
private function fromCache(){
$cached = true;
$clientCached = $_SERVER['HTTP_CACHE_CONTROL'];
if(!empty($clientCached)){
if($clientCached == 'no-cache' || $clientCached == 'must-revalidate' || $clientCached == 'max_age=0'){
$cached = false;
}
}
return $cached;
}
public function get($url, $opts){
if($this->redis->exists($url)){
if($this->fromCache()){
$data = $this->redis->get($url);
}else{
$data = file_get_contents($url, false, stream_context_create($opts));
}
}else{
$data = file_get_contents($url, false, stream_context_create($opts));
$this->redis->set($url, $data);
}
return $data;
}
}
.movie-card{
font-family: Roboto, sans-serif;
max-width: 320px;
width: 50vw;
padding: 30px 40px;
text-align: center;
}
.movie-card__header{
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
border: 1px solid lightgray;
background-color: whitesmoke;
text-align: left;
padding: 10px;
}
.movie-card__header__title{
line-height: 2em;
display: block;
font-weight: 500;
order: 1;
font-size:90%;
}
.movie-card__header__duration{
font-weight: lighter;
font-size:80%;
}
.movie-card__header__rotten-score{
display: flex;
font-size: 85%;
align-self: center;
}
.icon-img{
margin-left: 3px;
width: 20px;
height: 20px;
}
.movie-card__display-img{
max-width: 320px;
width: 50vw;
margin-bottom: -50px;
}
.movie-card__review-button{
text-decoration: none;
color: black;
background-color: gray;
border: 1px solid lightgray;
border-radius: 50ex;
padding: 10px 40px;
text-align: center;
margin-bottom: 200ex;
min-width: 100em;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.review-button:hover{
background-color: white;
border: 1px solid white;
}
.movie-card__movie-display{
margin-bottom: 0;
clear: both;
}
.movie-card__description{
font-weight: 300;
border: 1px solid lightgray;
background-color: whitesmoke;
text-align: left;
padding: 10px;
padding-top: 40px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>CrossKnowledge - Code challenge</title>
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">
<link href="component.css" rel="stylesheet">
</head>
<body>
<article class="movie-card">
<header class="movie-card__header">
<div>
<div class="movie-card__header__title">
Avengers: Infinity War
</div>
<div class="movie-card__header__duration">
156 minutes
</div>
</div>
<div class="movie-card__header__rotten-score">
85% <img class="icon-img" src="https://www.rottentomatoes.com/assets/pizza-pie/images/icons/global/cf-lg.3c29eff04f2.png" alt="">
</div>
</header>
<div class="movie-card__movie-display">
<img class="movie-card__display-img" src="https://i.ibb.co/9tLcR7s/avengers.jpg">
<a class="movie-card__review-button" href="https://www.rottentomatoes.com/m/avengers_infinity_war" target="_blank">
Check review
</a>
</div>
<footer>
<p class="movie-card__description">
An unprecedented cinematic journey ten years in the making and spanning the entire Marvel Cinematic Universe, Marvel Studios' "Avengers: Infinity War" brings to the screen the ultimate, deadliest showdown of all time. The Avengers and their Super Hero allies must be willing to sacrifice all in an attempt to defeat the powerful Thanos before his blitz of devastation and ruin puts an end to the universe.
</p>
</footer>
</article>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>CrossKnowledge - Code challenge</title>
</head>
<body>
<script src="human-readable-date.js" type="text/javascript"></script>
<script>
// This will create elements for testing, don't change this code
(() => {
const MS_PER_MINUTE = 60000
const NOW = new Date()
let minutes = [0, 1, 30, 60, 6 * 60, 23 * 60, 24 * 60]
let dates = []
minutes.forEach((i) => dates.push(new Date(NOW - i * MS_PER_MINUTE)))
dates.forEach((item) => {
let el = document.createElement("div")
el.innerHTML = "Started "
let dt = document.createElement('span')
dt.className = 'js-date-format'
dt.innerHTML = item.toISOString()
el.appendChild(dt)
document.body.appendChild(el)
})
})();
(() => {
humanReadable.initialize();
})();
</script>
</body>
</html>
var humanReadable = {
initialize : function (){
humanReadable.assign();
setInterval(function(){
humanReadable.loop();
}, 1000);
},
assign: function(){
let elements = document.getElementsByClassName('js-date-format')
for(let i = 0; i < elements.length; i++){
let date = new Date(elements[i].innerHTML);
elements[i].setAttribute('original_time', date.toISOString().split('.')[0]+"Z" );
}
},
find: function (){
let className = 'js-date-format';
let elements = document.getElementsByClassName(className);
return elements;
},
iterate: function(elements){
for(let i = 0; i < elements.length; i++){
elements[i].innerHTML = humanReadable.rewrite(elements[i].getAttribute('original_time'));
}
},
rewrite: function (info){
let date = new Date(info);
let now = new Date();
now = new Date(now.toISOString().split('.')[0]+"Z"); //correcting milliseconds discrepancies
let diff = Math.abs(date - now) / 1000;
if(diff < 60){
return diff + ' seconds ago';
}
else if(diff < 3600){
let minutes = Math.floor((diff / 60));
let desc = ((minutes == 1) ? 'minute' : 'minutes');
return `${minutes} ${desc} ago`;
}
else if(diff < 86400){
let hours = Math.floor((diff / 3600));
let desc = ((hours == 1) ? 'hour' : 'hours');
return `${hours} ${desc} ago`;
}
return info;
},
loop: function (){
humanReadable.iterate(humanReadable.find());
}
}
<?php
require ('Cache.php');
class SimpleJsonRequest
{
private static function makeRequest(string $method, string $url, array $parameters = null, array $data = null)
{
$opts = [
'http' => [
'method' => $method,
'header' => 'Content-type: application/json',
'content' => $data ? json_encode($data) : null
]
];
$url .= ($parameters ? '?' . http_build_query($parameters) : '');
$cache = new Cache();
return $cache->get($url, $opts);
}
public static function get(string $url, array $parameters = null)
{
return json_decode(self::makeRequest('GET', $url, $parameters));
}
public static function post(string $url, array $parameters = null, array $data)
{
return json_decode(self::makeRequest('POST', $url, $parameters, $data));
}
public static function put(string $url, array $parameters = null, array $data)
{
return json_decode(self::makeRequest('PUT', $url, $parameters, $data));
}
public static function patch(string $url, array $parameters = null, array $data)
{
return json_decode(self::makeRequest('PATCH', $url, $parameters, $data));
}
public static function delete(string $url, array $parameters = null, array $data = null)
{
return json_decode(self::makeRequest('DELETE', $url, $parameters, $data));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment