Browse Source

Added proxy controller

Joachim M. Giæver 2 years ago
parent
commit
f469127baf

+ 95 - 25
assets/js/regform.js

@@ -16,6 +16,7 @@ import {
     Dialog,
     DialogTitle,
     DialogContent,
+    DialogContentText,
     DialogActions,
     FormControl,
     FormControlLabel,
@@ -27,6 +28,11 @@ import {
     Typography,
 } from '@material-ui/core';
 
+import {
+    Alert,
+    AlertTitle
+} from '@material-ui/lab';
+
 import {
     Add,
     Remove,
@@ -51,22 +57,78 @@ const useStyles = makeStyles((theme) => ({
     }
 }));
 
-const SubmitButton = (props) => {
+const ReCaptchaSubmitButton = (props) => {
 
-    const [isApproved, setIsApproved] = useState(false);
     const recaptchaRef = React.createRef();
+    const [response, setResponse] = useState(null);
 
     const onChange = (value) => {
-        console.log("Captcha value:", value);
-        setIsApproved(true);
+        if (value) {
+            const request = {
+                organizer: props.inputs.organizer || null,
+                organizationNumber: props.inputs.organization_number || null,
+                contactPerson: props.inputs.contact_name || null,
+                contactEmail: props.inputs.contact_email || null,
+                contactPhone: props.inputs.contact_phone || null,
+                websiteUrl: props.inputs.contact_homepage || null,
+                place: props.inputs.event_location || null,
+                capacity: props.inputs.event_capacity || null,
+                happensOn: props.inputs.dates || [],
+                title: props.inputs.event_name || null,
+                image: props.inputs.event_photo || null,
+                description: props.inputs.event_description || null,
+                bookingUrl: props.inputs.event_booking || null,
+                audiences: (props.inputs.audiences || []).map(i => i.id),
+                categories: (props.inputs.categories || []).map(i => i.id),
+                captcha: value,
+            };
+
+            axios.post("https://magy.giaever.online/tff/api/webflow_collection_events", request).then(resp => {
+                setResponse({type: 'success', title: "Takk for ditt bidrag!", description: "Vi ser frem til din deltagelse."});
+                props.clearHandler();
+            }).catch(err => {
+                if (err.response) {
+                    setResponse({type: 'error', title: err.response.data['hydra:title'], description: err.response.data['hydra:description']});
+                } else if (err.request)
+                    setResponse({type: 'error', title: "En feil oppstod.", description: "Vennligst prøv igjen senere."});
+                else
+                    setResponse({type: 'error', title: "En feil oppstod,", description: err.message});
+            });
+        }
     }
 
     return (
         <React.Fragment>
             <center>
+            <Dialog open={response!= null} onClose={() => {
+                setResponse(null);
+                recaptchaRef.current.reset();
+            }}>
+                <DialogContent>
+                    <Alert severity={response ? response.type : "info"}>
+                        <AlertTitle>{response ? response.title : "unknown title"}</AlertTitle>
+                        {response ? (
+                            response.type == "error" ? 
+                                <ul>{response.description.split("\n").map((l, i) => <li key={i}>{l}</li>)}</ul>
+                                :
+                                response.description
+                            )
+                        : "this is the body"}
+                    </Alert>
+                </DialogContent>
+                <DialogActions>
+                    <Button onClick={() => {
+                        setResponse(null);
+                        recaptchaRef.current.reset();
+                        if (response.type != 'error')
+                            window.location.reload();
+                    }} color="secondary">
+                        Ok!
+                    </Button>
+                </DialogActions>
+            </Dialog>
                 <ReCAPTCHA
                     onChange={onChange}
-                    onExpired={() => isApproved ? setIsApproved(false) : false}
                     ref={recaptchaRef}
                     sitekey={"6LfRxgAVAAAAALlMOyD5h_a6gvWulxGqCC5gKJvd"}
                     size={"invisible"}
@@ -78,6 +140,7 @@ const SubmitButton = (props) => {
                 fullWidth
                 type={"submit"}
                 onClick={(e) => {
+                    e.preventDefault();
                     recaptchaRef.current.execute();
                 }}
             >Send skjema</Button>
@@ -90,7 +153,7 @@ const RegForm = (props) => {
     const classes = useStyles();
 
     const [inputs, updateInputs] = useState({
-        dates: [{date: new Date(), duration: 0}],
+        dates: [{date: new Date(), duration: 0.5}],
     });
 
     const inputHandler = (event) => {
@@ -98,7 +161,6 @@ const RegForm = (props) => {
         updateInputs((prevState) => ({...prevState, [target.id]: target.value}));
     };
 
-
     const inputCategoryHandler = (type, event, category) => {
         const keys = Object.keys(inputs).filter(idx => idx == type);
         let arr = keys.length == 0 ? [] : inputs[keys[0]];
@@ -138,21 +200,25 @@ const RegForm = (props) => {
             return;
         
         const target = e.target;
-        inputs.dates[idx].duration = parseInt(target.value, 10);
+        inputs.dates[idx].duration = (target.value >= 0 ? target.value * 1.0 : 0.5).toFixed(2);
 
         updateInputs((prevState) => ({...prevState, dates: inputs.dates}));
     }
 
-    const submitHandler = (event) => {
-        event.PreventDefault();
-        console.log(event);
+    let formRef = React.createRef();
+
+    const clearInputs = () => {
+        formRef.current.reset();
+        updateInputs((prevState) => ({
+            dates: [{date: new Date(), duration: 0.5}]
+        }));
     }
 
     return (
         <Container component="main" maxWidth="md">
             <CssBaseline />
             <Paper className={classes.paper}>
-                <form className={classes.form}>
+                <form className={classes.form} ref={formRef}>
                     <FormControl component={"fieldset"} className={classes.fieldset}>
                         <Typography component={"legend"} variant={"h5"}>
                             Om arrangøren
@@ -161,7 +227,7 @@ const RegForm = (props) => {
                             <Grid item xs={12} sm={7} md={8}>
                                 <TextField 
                                     id={"organizer"}
-                                    label={"Navn på arrangør"}
+                                    label={"Navn"}
                                     onChange={inputHandler}
                                     required
                                     fullWidth
@@ -209,8 +275,9 @@ const RegForm = (props) => {
                             <Grid item xs={12} sm={6}>
                                 <TextField
                                     id={"contact_homepage"}
-                                    label={"Hjemmesideadresse"}
+                                    label={"Hjemmesideadresse (husk «http://»)"}
                                     placeholder={"https:// ...."}
+                                    onChange={inputHandler}
                                     required
                                     fullWidth
                                 />
@@ -225,7 +292,7 @@ const RegForm = (props) => {
                             <Grid item xs={12}>
                                 <TextField
                                     id={"event_name"}
-                                    label={"Navn på arrangement"}
+                                    label={"Tittel på arrangement"}
                                     onChange={inputHandler}
                                     required
                                     fullWidth
@@ -248,11 +315,12 @@ const RegForm = (props) => {
                                     <Grid item xs={4}>
                                         <TextField
                                             id={"event_duration"}
-                                            label={"Varighet (timer)"}
+                                            label={`Varighet (${date.duration} t = ${(date.duration * 60.0).toFixed(0)} min)`}
                                             onChange={e => dateDurationSaveHandler(idx, e)}
+                                            inputProps={{step: 0.25}}
                                             type={"number"}
                                             placeholder={"antall timer"}
-                                            value={date.duration}
+                                            value={date.duration ? date.duration : 0.5}
                                             fullWidth
                                         />
                                     </Grid>
@@ -278,9 +346,8 @@ const RegForm = (props) => {
                                 <TextField
                                     id={"event_capacity"}
                                     label={"Kapasitet"}
-                                    placeholder={"Antall personer"}
+                                    placeholder={"For eks. antall personer"}
                                     onChange={inputHandler}
-                                    type={"number"}
                                     required
                                     fullWidth
                                 />
@@ -299,7 +366,7 @@ const RegForm = (props) => {
                             <Grid item xs={12} sm={6}>
                                 <TextField
                                     id="event_photo"
-                                    label={"Photo-link til arrangement"}
+                                    label={"Photo-link til arrangement (husk «http://»)"}
                                     onChange={inputHandler}
                                     placeholder={"Kan for eks. være en bilde-fil eller mappe med bilder i Dropbox."}
                                     fullWidth
@@ -309,7 +376,7 @@ const RegForm = (props) => {
                             <Grid item xs={12} sm={6}>
                                 <TextField
                                     id="event_booking"
-                                    label={"Link til booking"}
+                                    label={"Link til booking (husk «http://»)"}
                                     onChange={inputHandler}
                                     placeholder={"https:// ..."}
                                     fullWidth
@@ -318,7 +385,7 @@ const RegForm = (props) => {
                             </Grid>
                             <Grid item xs={12} sm={6}>
                                 <CategorySelection 
-                                    cid={"5ed0f77a42f75848a95ebf03"} 
+                                    cid={"/tff/api/webflow_collection_categories"} 
                                     category="Kategorier"
                                     name="categories"
                                     selected={inputs.categories || []}
@@ -327,7 +394,7 @@ const RegForm = (props) => {
                             </Grid>
                             <Grid item xs={12} sm={6}>
                                 <CategorySelection 
-                                    cid={"5ed10cffd2b21cfb3b269088"} 
+                                    cid={"/tff/api/webflow_collection_audiences"} 
                                     category="Målgrupper"
                                     name="audiences"
                                     selected={inputs.audiences || []}
@@ -338,7 +405,10 @@ const RegForm = (props) => {
                     </FormControl>
                     <Grid container justify={"center"}>
                         <Grid item xs={8}>
-                            <SubmitButton text={"Send skjema"}/>
+                            <ReCaptchaSubmitButton text={"Send skjema"} 
+                                inputs={inputs} 
+                                clearHandler={clearInputs}
+                            />
                         </Grid>
                     </Grid>
                 </form>
@@ -353,7 +423,7 @@ const CategorySelection = (props) => {
     const [availableItems, setAvailableItems] = useState([]);
 
     React.useEffect(() => {
-        axios.get("https://magy.giaever.online/tff/api/webflow_items?cid=" + props.cid)
+        axios.get("https://magy.giaever.online" + props.cid)
             .then((response) => {
                 const data = response.data['hydra:member'];
                 setAvailableItems(response.data['hydra:member']);

+ 10 - 10
composer.lock

@@ -411,16 +411,16 @@
         },
         {
             "name": "doctrine/common",
-            "version": "2.13.2",
+            "version": "2.13.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/common.git",
-                "reference": "6902fafacb43333d9dc3d949c0a06048a31549ac"
+                "reference": "f3812c026e557892c34ef37f6ab808a6b567da7f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/common/zipball/6902fafacb43333d9dc3d949c0a06048a31549ac",
-                "reference": "6902fafacb43333d9dc3d949c0a06048a31549ac",
+                "url": "https://api.github.com/repos/doctrine/common/zipball/f3812c026e557892c34ef37f6ab808a6b567da7f",
+                "reference": "f3812c026e557892c34ef37f6ab808a6b567da7f",
                 "shasum": ""
             },
             "require": {
@@ -490,7 +490,7 @@
                 "doctrine",
                 "php"
             ],
-            "time": "2020-05-29T17:35:20+00:00"
+            "time": "2020-06-05T16:46:05+00:00"
         },
         {
             "name": "doctrine/dbal",
@@ -1449,16 +1449,16 @@
         },
         {
             "name": "nikic/php-parser",
-            "version": "v4.4.0",
+            "version": "v4.5.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/nikic/PHP-Parser.git",
-                "reference": "bd43ec7152eaaab3bd8c6d0aa95ceeb1df8ee120"
+                "reference": "53c2753d756f5adb586dca79c2ec0e2654dd9463"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bd43ec7152eaaab3bd8c6d0aa95ceeb1df8ee120",
-                "reference": "bd43ec7152eaaab3bd8c6d0aa95ceeb1df8ee120",
+                "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/53c2753d756f5adb586dca79c2ec0e2654dd9463",
+                "reference": "53c2753d756f5adb586dca79c2ec0e2654dd9463",
                 "shasum": ""
             },
             "require": {
@@ -1497,7 +1497,7 @@
                 "parser",
                 "php"
             ],
-            "time": "2020-04-10T16:34:50+00:00"
+            "time": "2020-06-03T07:24:19+00:00"
         },
         {
             "name": "ocramius/package-versions",

+ 1 - 0
package.json

@@ -18,6 +18,7 @@
         "@date-io/date-fns": "1.x",
         "@material-ui/core": "^4.10.1",
         "@material-ui/icons": "^4.9.1",
+        "@material-ui/lab": "^4.0.0-alpha.55",
         "@material-ui/pickers": "^3.2.10",
         "axios": "^0.19.2",
         "date-fns": "^2.14.0",

+ 16 - 21
src/Controller/IndexController.php

@@ -2,43 +2,38 @@
 
 namespace App\Controller;
 
+use App\Entity\ItemSerializedName;
+use App\Entity\WebflowCollectionEvent;
 use App\Http\WebflowApi\WebflowApiCollection;
 use App\Http\WebflowApi\WebflowApiCollections;
 use App\Http\WebflowApi\WebflowSites;
 use App\Http\WebflowApi\WebflowSite;
 use App\Http\WebflowApiClient;
+use App\Serializer\ItemSerializer;
+use Doctrine\Common\Annotations\Reader;
 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Routing\Annotation\Route;
+use Symfony\Component\Serializer\Annotation\SerializedName;
 
 class IndexController extends AbstractController
 {
     /**
      * @Route("/", name="index")
      */
-    public function index(WebflowSites $wfsites, WebflowApiClient $wfapi)
+    public function index(WebflowSites $wfsites, WebflowApiClient $wfapi, Reader $reader)
     {
-        dump($wfsites->getSiteByShortName("tromsup"));
-        $site = WebflowSite::byId($wfapi, '5ebabfe546c816388d66c03a')->load();
-        $site = WebflowSite::byId($wfapi, '5ebabfe546c816388d66c03a')->load();
-        $site = WebflowSite::byId($wfapi, '5ebabfe546c816388d66c03a')->load();
-        $site = WebflowSite::byId($wfapi, '5ebabfe546c816388d66c03a')->load();
-        $site = WebflowSite::byId($wfapi, '5ebabfe546c816388d66c03a')->load();
-
-        dump($site);
-
-        dump($site->getCollections());
-        dump((new WebflowApiCollections($wfapi, $site)));
-
-        $coll = WebflowApiCollection::byId($wfapi, '5ebbafce7b593028a5f8f29a');
-
-        dump($coll);
-
-        $items = $coll->getItems();
-
-        dump($items);
-
         return $this->render('index/index.html.twig', [
             'controller_name' => 'IndexController',
         ]);
     }
+
+    /**
+     * @Route(
+     *  "/proxy/{type}/{name}",
+     * )
+    public function proxyCategory(): Response {
+        return null;
+    }
+     */
 }

+ 29 - 20
src/DataProvider/WebflowDataProvider.php

@@ -6,10 +6,17 @@ use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
 use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface;
 use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
 use App\Entity\WebflowCollection;
-use App\Entity\WebflowItem;
+use App\Entity\WebflowCollectionAudience;
+use App\Entity\WebflowCollectionCategory;
+use App\Entity\WebflowCollectionDates;
+use App\Entity\WebflowCollectionEvent;
 use App\Http\WebflowApi\WebflowApiCollection;
 use App\Http\WebflowApi\WebflowSite;
+use App\Http\WebflowApi\WebflowSites;
 use App\Http\WebflowApiClient;
+use App\Serializer\ItemSerializer;
+use Doctrine\Common\Annotations\Reader;
+use phpDocumentor\Reflection\Types\Resource_;
 use Psr\Log\LoggerInterface;
 
 final class WebflowDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface {
@@ -17,18 +24,24 @@ final class WebflowDataProvider implements ContextAwareCollectionDataProviderInt
     private $site;
     private $apiClient;
     private $logger;
+    private $reader;
 
-    public function __construct(WebflowApiClient $webflowApiClient, LoggerInterface $l) {
-        $this->site = WebflowSite::byId($webflowApiClient, '5ebabfe546c816388d66c03a');
+    public function __construct(WebflowApiClient $webflowApiClient, LoggerInterface $l, Reader $reader) {
+        $this->site = WebflowSite::byId(new WebflowSites($webflowApiClient), '5ebabfe546c816388d66c03a');
         $this->apiClient = $webflowApiClient;
         $this->logger= $l;
+        $this->reader = $reader;
     }
 
     public function supports(string $resourceClass, ?string $operationName = null, array $context = []): bool
     {
+        $this->logger->debug(__METHOD__, [$resourceClass, $operationName, $context]);
         return in_array($resourceClass, [
             WebflowCollection::class,
-            WebflowItem::class,
+            WebflowCollectionAudience::class,
+            WebflowCollectionCategory::class,
+            WebflowCollectionDates::class,
+            WebflowCollectionEvent::class,
         ]);
     }
 
@@ -36,25 +49,21 @@ final class WebflowDataProvider implements ContextAwareCollectionDataProviderInt
     {
         switch ($resourceClass) {
         case WebflowCollection::class:
-            foreach($this->site->getCollections() as $col)
-                yield new WebflowCollection($col);
-            break;
-        case WebflowItem::class:
-
-            if (isset($context['filters']) && isset($context['filters']['cid'])) {
-                $col = WebflowApiCollection::byId($this->apiClient, $context['filters']['cid']);
-
-                foreach ($col->getItems() as $item)
-                    yield new WebflowItem($item);
-
-                break;
+            foreach($this->site->getCollections() as $col) {
+                $class =  WebflowCollection::fromClient($col, $this->reader);
+                yield $class;
             }
-
-            foreach ($this->site->getCollections() as $col)
-                foreach ($col->getItems() as $item)
-                    yield new WebflowItem($item);
             break;
+        case WebflowCollectionAudience::class:
+        case WebflowCollectionCategory::class:
+        case WebflowCollectionDates::class:
+        case WebflowCollectionEvent::class:
+            $col = WebflowApiCollection::byId($this->site, $resourceClass::cid())->load();
 
+            foreach ($col->getItems() as $item) {
+                 $class = $resourceClass::fromClient($item, $this->reader);
+                 yield $class;
+            }
         }
 
         return null;

+ 6 - 3
src/DataProvider/WebflowItemDataProvider.php

@@ -5,6 +5,8 @@ namespace App\DataProvider;
 use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
 use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
 use App\Entity\WebflowCollection;
+use App\Entity\WebflowItem;
+use App\Entity\WebflowItemCategory;
 use App\Http\WebflowApi\WebflowSite;
 use App\Http\WebflowApi\WebflowSites;
 use App\Http\WebflowApiClient;
@@ -23,6 +25,7 @@ final class WebflowItemDataProvider implements ItemDataProviderInterface, Restri
     }
 
     public function supports(string $resourceClass, ?string $operationName = null, array $context = []): bool {
+        $this->logger->debug(__METHOD__, [$resourceClass, $operationName, $context]);
         return in_array($resourceClass, [
             WebflowCollection::class,
             WebflowItem::class,
@@ -30,12 +33,12 @@ final class WebflowItemDataProvider implements ItemDataProviderInterface, Restri
     }
 
     public function getItem(string $resourceClass, $id, ?string $operationName = null, array $context = []): ?WebflowCollection {
-
         switch ($resourceClass) {
         case WebflowCollection::class:
             foreach ($this->site->getCollections() as $col)
-                if ($col->data['_id'] == $id)
-                    return new WebflowCollection($col);
+                if ($col->data['_id'] == $id) {
+                    return new WebflowCollection($col->load(true));
+                }
             break;
         }
     

+ 0 - 0
src/Entity/.gitignore


+ 0 - 60
src/Entity/AbstractEntity.php

@@ -1,60 +0,0 @@
-<?php
-
-namespace App\Entity;
-
-abstract class AbstractEntity {
-    
-    private $name;
-
-    private $slug;
-
-    private $data = [];
-
-    protected function setFromData(array $data) {
-        foreach ($data as $key => $value) {
-
-            $prop = ltrim($key, '_');
-            $method = sprintf("set%s", ucfirst(
-                $prop
-            ));
-
-            if (method_exists($this, $method)) {
-                [$this, $method]($value);
-            } elseif (property_exists($this, $prop)) {
-                $this->{$prop} = $value;
-            } else {
-                if (property_exists($this, 'data')) {
-                    if (isset($this->data[$key]))
-                        continue;
-
-                    if (strpos($key, '-') !== false)
-                        continue;
-
-                    $this->data[$key] = $value;
-                }
-            }
-        }
-    }
-
-    public function getName(): ?string {
-        return $this->name;
-    }
-
-    public function setName(string $name): self {
-        $this->name = $name;
-        return $this;
-    }
-
-    public function getSlug(): ?string {
-        return $this->slug;
-    }
-
-    public function setSlug(string $slug): self {
-        $this->slug = $slug;
-        return $this;
-    }
-
-    public function getData(): ?array {
-        return $this->data;
-    }
-}

+ 0 - 8
src/Entity/EntityInterface.php

@@ -1,8 +0,0 @@
-<?php
-
-namespace App\Entity;
-
-interface EntityInterface {
-}
-
-?>

+ 38 - 15
src/Entity/WebflowCollection.php

@@ -4,19 +4,21 @@ namespace App\Entity;
 
 use ApiPlatform\Core\Annotation\ApiProperty;
 use ApiPlatform\Core\Annotation\ApiResource;
-use App\Http\WebflowApi\WebflowApiCollection;
-use DateTimeImmutable;
+use ApiPlatform\Core\Annotation\ApiSubresource;
+use App\Serializer\ItemSerializedName;
+use Symfony\Component\Serializer\Annotation\Groups;
 
 /**
  * @ApiResource(
+ *  itemOperations={"get"},
  *  collectionOperations={"get"},
- *  itemOperations={"get"}
  * )
  */
-class WebflowCollection extends AbstractEntity {
+class WebflowCollection extends AbstractWebflowEntity {
 
     /**
      * @ApiProperty(identifier=true)
+     * @ItemSerializedName("_id")
      */
     protected $id;
 
@@ -26,32 +28,53 @@ class WebflowCollection extends AbstractEntity {
 
     private $singularName;
 
-    protected $lastUpdated;
-    protected $createdOn;
+    private $lastUpdated;
 
-    public function __construct(WebflowApiCollection $webflowApiCollection) {
-        $this->setFromData($webflowApiCollection->data);
+    private $createdOn;
+
+    public function getName(): ?string {
+        return $this->name;
+    }
+
+    public function setName(string $name): self {
+        $this->name = $name;
+        return $this;
     }
 
-    public function getId(): ?string {
-        return $this->id;
+    public function getSlug(): ?string {
+        return $this->slug;
+    }
+
+    public function setSlug(string $name): self {
+        $this->slug = $name;
+        return $this;
     }
 
     public function getSingularName(): ?string {
         return $this->singularName;
     }
 
-    public function setSingularName(string $sn): self {
-        $this->singularName = $sn;
+    protected function setSingularName(string $name): self {
+        $this->singularName = $name;
         return $this;
     }
 
     public function getLastUpdated(): \DateTimeInterface {
-        return new DateTimeImmutable($this->lastUpdated);
+        return $this->lastUpdated ?? new \DateTimeImmutable($this->lastUpdated);
+    }
+
+    protected function setLastUpdated(string $data): self {
+        $this->lastUpdated = new \DateTimeImmutable($data);
+        return $this;
     }
 
     public function getCreatedOn(): \DateTimeInterface {
-        return new DateTimeImmutable($this->lastUpdated);
+        return $this->createdOn ?? new \DateTimeImmutable($this->createdOn);
     }
-}
 
+    protected function setCreatedOn(string $data): self {
+        $this->createdOn = new \DateTimeImmutable($data);
+        return $this;
+    }
+
+}

+ 0 - 62
src/Entity/WebflowItem.php

@@ -1,62 +0,0 @@
-<?php
-
-namespace App\Entity;
-
-use ApiPlatform\Core\Annotation\ApiFilter;
-use ApiPlatform\Core\Annotation\ApiProperty;
-use ApiPlatform\Core\Annotation\ApiResource;
-use App\Http\WebflowApi\WebflowApiCollectionItem;
-use App\Filter\SearchFilter;
-
-/**
- * @ApiResource(
- *  collectionOperations={"get", "post"},
- *  itemOperations={"get"}
- * )
- * @ApiFilter(SearchFilter::class, properties={"cid": "exact"})
- */
-class WebflowItem extends AbstractEntity {
-
-    /**
-     * @ApiProperty(identifier=true)
-     */
-    protected $id;
-
-    private $cid;
-
-    private $reCaptcha = null;
-
-    protected $archived;
-
-    protected $draft;
-
-    public function __construct(WebflowApiCollectionItem $webflowApiCollectionItem) {
-        $this->setFromData($webflowApiCollectionItem->data);
-    }
-
-    public function getId(): ?string {
-        return $this->id;
-    }
-
-    public function getCid(): ?string {
-        return $this->cid;
-    }
-
-    public function setCid(string $cid): self {
-        $this->cid = $cid;
-        return $this;
-    }
-
-    public function getArchived(): bool {
-        return $this->archived;
-    }
-
-    public function getDraft(): bool {
-        return $this->draft;
-    }
-
-    public function setReCaptcha(string $value) {
-        $this->reCaptcha = $value;
-    }
-
-}

+ 4 - 0
src/Http/WebflowApi/AbstractWebflowApiClient.php

@@ -16,5 +16,9 @@ abstract class AbstractWebflowApiClient {
         return $this->client;
     }
 
+    public function getLastResponseHeaders(): ?array {
+        return $this->client->lastResponse;
+    }
+
 }
 

+ 6 - 5
src/Http/WebflowApi/AbstractWebflowApiField.php

@@ -4,6 +4,7 @@ namespace App\Http\WebflowApi;
 
 use App\Http\WebflowApiClient;
 use RuntimeException;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
 abstract class AbstractWebflowApiField extends AbstractWebflowApiClient {
 
@@ -19,8 +20,8 @@ abstract class AbstractWebflowApiField extends AbstractWebflowApiClient {
         $this->initialized = !is_null($data);
     }
 
-    public static function byId(WebflowApiClient $wfapi, string $id): AbstractWebflowApiField {
-        $new = new static($wfapi, null);
+    public static function byId(AbstractWebflowApiClient $wfapi, string $id, array $options = []): AbstractWebflowApiField {
+        $new = new static($wfapi->getClient(), null);
         $new->data['_id'] ??= $id;
         return $new;
     }
@@ -29,11 +30,11 @@ abstract class AbstractWebflowApiField extends AbstractWebflowApiClient {
         return $this->initialized;
     }
 
-    public function load(): self {
-        if ($this->initialized)
+    public function load(bool $reload = false): self {
+        if ($this->initialized && !$reload)
             return $this;
 
-        $req = $this->getClient()->get($this->getLoadScope());
+        $req = $this->getClient()->get($this->getLoadScope(), $reload ? -1 : 300);
 
         if ($req->getStatusCode() == 200) {
             $this->initialized = true;

+ 7 - 0
src/Http/WebflowApi/WebflowApiCollection.php

@@ -16,4 +16,11 @@ class WebflowApiCollection extends AbstractWebflowApiField {
         );
     }
 
+    public function createItem(array $data): WebflowApiCollectionItem {
+        $resp = $this->getClient()->post(
+            $this->getLoadScope() . DIRECTORY_SEPARATOR . "items",
+            $data
+        );
+        return new WebflowApiCollectionItem($this->getClient(), $resp->toArray());
+    }
 }

+ 24 - 2
src/Http/WebflowApi/WebflowApiCollectionItem.php

@@ -2,10 +2,32 @@
 
 namespace App\Http\WebflowApi;
 
-class WebflowApiCollectionItem extends AbstractWebflowApiField {
+use App\Entity\AbstractWebflowCollectionItem;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
+class WebflowApiCollectionItem extends AbstractWebflowApiField {
+    
     protected function getLoadScope(): string
     {
-        return sprintf('collection/%s/items/%s', "", "");
+        return sprintf('collections/%s/items/%s', $this->data['_cid'], $this->data['_id']);
+    }
+
+    public static function byId(AbstractWebflowApiClient $wfapi, string $id, array $options = []): AbstractWebflowApiField
+    {
+        $new = parent::byId($wfapi, $id);
+        $new->data['_cid'] = $options['_cid'] ?? null;
+        return $new;
     }
+
+    public function load(bool $reload = false): self {
+        $oldData = $this->data;
+
+        parent::load($reload)->data = array_pop($this->data['items']);
+        
+        if (empty($this->data))
+            throw new NotFoundHttpException(sprintf("Item with ID %s (cID %s) is not found", $oldData['_id'], $oldData['_cid']));
+
+        return $this;
+    }
+
 }

+ 0 - 1
src/Http/WebflowApi/WebflowSites.php

@@ -2,7 +2,6 @@
 
 namespace App\Http\WebflowApi;
 
-
 class WebflowSites extends AbstractWebflowApiCollection {
 
     public function getLoadScope(): string {

+ 66 - 10
src/Http/WebflowApiClient.php

@@ -2,23 +2,24 @@
 
 namespace App\Http;
 
+use Psr\Log\LoggerAwareInterface;
+use Symfony\Component\Cache\Adapter\FilesystemAdapter;
 use Symfony\Component\Cache\Adapter\TraceableAdapter;
-use Symfony\Component\HttpClient\CachingHttpClient;
 use Symfony\Component\HttpClient\HttpClient;
 use Symfony\Component\HttpClient\Response\MockResponse;
-use Symfony\Component\HttpKernel\HttpCache\Store;
+use Symfony\Contracts\Cache\CacheInterface;
 use Symfony\Contracts\Cache\ItemInterface;
-use Symfony\Contracts\HttpClient\HttpClientInterface;
 use Symfony\Contracts\HttpClient\ResponseInterface;
 
-class WebflowApiClient {
+class WebflowApiClient implements LoggerAwareInterface {
 
     private $client;
     private $cache;
     private $url;
     private $options = [];
+    public $lastResponse;
 
-    public function __construct(TraceableAdapter $cache, string $token, string $api_url, string $version = '1.0.0', string $cache_dir) {
+    public function __construct(CacheInterface $cache, string $token, string $api_url, string $version = '1.0.0') {
 
         $this->url = $api_url;
 
@@ -35,7 +36,6 @@ class WebflowApiClient {
         );
         
         $this->cache = $cache;
-        dump($this->cache);
     }
 
 
@@ -47,15 +47,58 @@ class WebflowApiClient {
         return "webflow_api_" . sha1($scope_string);
     }
 
-    public function get(string $scope, int $ttl = 300): ResponseInterface {
+    public function post(string $scope, array $data): ResponseInterface {
+        $data = array_filter($data, function ($in) {
+            if ($in === false)
+                return true;
 
+            return !empty($in);
+        });
+
+        $resp = $this->client->request('POST', $this->scopeFromBase(
+            $scope
+        ), ['json' => [
+            "fields" => $data
+        ]]);
+
+        $this->lastResponse = $resp->getHeaders();
+
+        $this->logger->debug(__METHOD__, [
+            $resp->getStatusCode(), $resp->toArray(), $data
+        ]);
+
+        return $resp;
+    }
+
+    public function get(string $scope, int $ttl = 1800): ResponseInterface {
+
+        $this->logger->debug(__METHOD__, ['ttl' => $ttl, 'scope' => $scope]);
         $id = $this->scopeId($scope);
 
-        if ($ttl <= 0)  
-            return $this->client->request('GET',
+        if ($ttl <= 0) {
+
+            $resp = $this->client->request('GET',
                 $this->scopeFromBase($scope),
             );
 
+            $this->lastResponse = $resp->getHeaders();
+
+            if ($resp->getStatusCode() >= 300)
+                return $resp;
+
+            $this->cache->get($id, function(ItemInterface $item) use ($scope, $ttl, $resp) {
+                $item->set([
+                    'header' => $resp->getHeaders(),
+                    'content' => $resp->getContent(),
+                ]);
+                $this->cache->save($item);
+            });
+
+            $this->cache->commit();
+
+            return $resp;
+        }
+
         $item = $this->cache->get($id, function (ItemInterface $item) use ($scope, $ttl) {
             $item->expiresAfter($ttl);
 
@@ -63,8 +106,10 @@ class WebflowApiClient {
                 $this->scopeFromBase($scope),
             );
 
+            $this->lastResponse = $response->getHeaders();
+
             if ($response->getStatusCode() >= 300)
-                return;
+                return ['header' => $response->getHeaders(), "content" => $response->getContent()];
 
             $item->set([
                 'header' => $response->getHeaders(),
@@ -77,9 +122,20 @@ class WebflowApiClient {
         });
 
         $this->cache->commit();
+
+        $this->logger->debug(__METHOD__, [$scope, array_keys($item)]);
+
+        if (!$item)
+            return $this->get($scope, -1);
+
+        $this->lastResponse = $item['header'];
         return MockResponse::fromRequest('GET', $scope, $item['header'], new MockResponse($item['content']));
     }
 
+    public function setLogger(\Psr\Log\LoggerInterface $logger)
+    {
+        $this->logger = $logger;
+    }
 }
 
 ?>

+ 11 - 0
yarn.lock

@@ -918,6 +918,17 @@
   dependencies:
     "@babel/runtime" "^7.4.4"
 
+"@material-ui/lab@^4.0.0-alpha.55":
+  version "4.0.0-alpha.55"
+  resolved "https://registry.yarnpkg.com/@material-ui/lab/-/lab-4.0.0-alpha.55.tgz#82a850dc59654e04ee3a31be1b34eb3bf64d5585"
+  integrity sha512-mPHiS52Z1AT6NlWNp07xxTkoKGU3DZhoGdVtLKGy2+uMH5t4/UPtiZLRab7hR3hvuHvguxgV4tkBC9ww3xqUzA==
+  dependencies:
+    "@babel/runtime" "^7.4.4"
+    "@material-ui/utils" "^4.9.6"
+    clsx "^1.0.4"
+    prop-types "^15.7.2"
+    react-is "^16.8.0"
+
 "@material-ui/pickers@^3.2.10":
   version "3.2.10"
   resolved "https://registry.yarnpkg.com/@material-ui/pickers/-/pickers-3.2.10.tgz#19df024895876eb0ec7cd239bbaea595f703f0ae"