Создание планировщика Twitter с помощью React и Hasura
![](/static/storage/146321166019242864698741942262951264773.png)
В этом руководстве вы узнаете, как создать планировщик твитов. Введите свои учетные данные Twitter, выберите время и дату, и ваш твит будет опубликован в это время.
Rocketgraph: Замена Firebase с открытым исходным кодом
Краткая информация о нас. Rocketgraph позволяет разработчикам создавать веб-приложения за считанные минуты.
Для этого мы предоставляем:
- PostgresDB, размещенный на AWS RDS.
- Консоль Hasura для GraphQL-анализа PostgresDB, управления разрешениями, кронами, планировщиками и многим другим.
- Встроенная аутентификация, поддерживающая электронную почту/пароль, социальную сеть, волшебную ссылку и одноразовый пароль.
- Бессерверные функции для любого дополнительного кода бэкенда.
Прежде чем мы начнём!
Мы будем использовать планировщики включения-выключения Hasura для запуска Twitter API в нужное время и пограничные функции Rocketgraph для настройки бэкенда. Итак, нам нужно создать проект на Rocketgraph. Это бесплатно.
Регистрация
Создание проекта
![](/static/storage/85593609877107003288294913397884915181.png)
Это позволит настроить консоль Hasura. На данный момент вам не нужно знать, как ею пользоваться. Поскольку мы будем использовать ее через API Hasura.
Давайте всё настроим!
Мы будем использовать React.js и shadcn UI для фронт-энда и Node.js для наших бессерверных функций.
Создайте папку для проекта, клиента и сервера:
mkdir twitter-app && cd twitter-app
mkdir client server
Настройка лямбда-функций
С помощью Rocketgraph вы можете легко создавать лямбда-функции, которые выступают в качестве бэкенда. Для настройки запишите название проекта в консоли проекта на Rocketgraph:
npm init -y
npm install express body-parser axios axios-oauth-1.0a
npm install @codegenie/serverless-express
Последняя установка предназначена для экспресс-развертывания лямбд AWS. Вам не нужно беспокоиться об этом, так как Rocketgraph позаботится о развертывании лямбд.
Создайте файл index.js
и добавьте в него следующий кодa:
const serverlessExpress = require('@codegenie/serverless-express')
const app = require('./app')
exports.handler = serverlessExpress({ app })
Теперь создайте файл с именем app.js
и добавьте в него следующий код:
// jshint esversion: 6
const express = require("express");
const bodyParser = require("body-parser");
const axios = require("axios");
const addOAuthInterceptor = require("axios-oauth-1.0a");
const app = express();
const port = 3000
app.use(express.json());
const APP_URL = "https://guhys2s4uv24wbh6zoxl3by4xa0im.lambda-url.us-east-2.on.aws/tweet";
app.post("/tweet", function (req, res) {
// Create a client whose requests will be signed
const client = axios.create();
// Specify the OAuth options
const payload = req.body.payload;
const options = {
algorithm: 'HMAC-SHA1',
key: `${payload.key}`,
secret: `${payload.secret}`,
token: `${payload.token}`,
tokenSecret: `${payload.tokenSecret}`,
};
const data = {
"text": `${payload.tweet}`
};
console.log("addOAuthInterceptor is not a function: ", addOAuthInterceptor)
// Add interceptor that signs requests
addOAuthInterceptor.default(client, options);
console.log("params: ", req.body);
client.post("https://api.twitter.com/2/tweets",
data)
.then(response => {
console.log("resp: ", response, req.body.key);
res.status(200).send(`<h2> the result is : Tweeted </h2>` );
})
.catch(err => {
console.log(err);
res.status(500).send(`<h2> the result is : Error </h2>` );
});
});
app.post("/schedule-tweet", function (req, res) {
const body = JSON.parse(req.body);
console.log("body", body)
const jsonb = {
"type": "create_scheduled_event",
"args": {
"webhook": `${APP_URL}`,
"schedule_at": `${body.schedule_at}`,
"include_in_metadata": true,
"payload": {
"tweet": `${body.tweet}`,
"algorithm": 'HMAC-SHA1',
"key": `${body.key}`,
"secret": `${body.secret}`,
"token": `${body.token}`,
"tokenSecret": `${body.tokenSecret}`,
},
"comment": "sample scheduled event comment"
}
}
console.log(req.body);
axios.post("https://hasura-vlklxvo.rocketgraph.app/v1/metadata", jsonb, {
"headers" : {
"Content-Type": "application/json",
"X-Hasura-Role":"admin",
"X-hasura-admin-secret":"********"
},
}).then(function (response) {
console.log("response: ", response);
res.status(200).send({
message: "Your result is tweeted"
})
}).catch(function (error) {
console.log("Error from Hasura: ", error);
res.status(500).send(error)
});
})
app.listen(port, () => console.log(`calculator listening on port ${port}!`))
module.exports = app;
Заметили APP_URL
? Оставьте его пока пустым. Мы получим этот URL, когда развернем функцию. Поэтому сейчас давайте сделаем это.
Также замените X-hasura-admin-secret
и HASURA_URL
на те, что указаны на вкладке Hasura вашего проекта
Развертывание функции
Rgraph предоставляет симпатичный CLI, который поможет вам разрабатывать свои функции для AWS лямбд:
npm install -g rgraph@0.1.10
И, указав имя вашего проекта, выполните это, чтобы развернуть вашу функцию:
rgraph deploy <function name>
Создание фронтенда
Создайте проект:
cd client
npx create-next-app@latest my-app --typescript --tailwind --eslint
Добавьте shadcn ui:
npx shadcn-ui@latest init
Добавьте необходимые компоненты для нашего приложения:
npx shadcn-ui@latest add button popover calendar card input label
Создание пользовательского интерфейса
В файле src/app/page.tsx
:
"use client"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import React from "react"
import { useState } from "react"
import { DatePickerDemo } from "./date"
type ScheduleTweetInput = {
key: String,
secret: String,
token: String,
tokenSecret: String,
tweet: String,
date: any,
time: any,
}
const sendTweet = async (twitterData: ScheduleTweetInput) => {
const { date, time, tweet, key, secret, token, tokenSecret } = twitterData;
console.log(time.split(":"))
date.setHours(...time.split(":"))
console.log(twitterData, date);
const isoDate = date.toISOString()
console.log(isoDate);
const url = "https://guhys2s4uv24wbh6zoxl3by4xa0imsdb.lambda-url.us-east-2.on.aws/schedule-tweet"
const postData = {
"tweet": tweet,
"schedule_at": isoDate,
"key": key,
"secret": secret,
"token": token,
"tokenSecret": tokenSecret
}
let headersList = {
"Accept": "*/*",
"User-Agent": "Thunder Client (https://www.thunderclient.com)",
"Content-Type": "application/json"
}
let bodyContent = JSON.stringify(postData);
let response = await fetch(url, {
method: "POST",
body: bodyContent,
headers: headersList,
mode: "no-cors"
});
let data = await response.text();
console.log(data, response.status, response.json);
console.log("Status", response.status, response);
}
export default function DemoCreateAccount() {
const [key, setKey] = useState("");
const [token, setToken] = useState("");
const [tokenSecret, setTokenSecret] = useState("");
const [tweet, setTweet] = useState("");
const [secret, setSecret] = useState("");
const [date, setDate] = React.useState<Date>()
const [time, setTime] = useState<any>('10:00');
console.log(key, "key")
return (
<Card>
<CardHeader className="space-y-1">
<CardTitle className="text-2xl">Twitter scheduler</CardTitle>
<CardDescription>
Enter your credentials below to schedule a tweet
</CardDescription>
</CardHeader>
<CardContent className="grid gap-4">
<div className="grid gap-2">
<Label htmlFor="email">API Key</Label>
<Input id="email" type="email" placeholder="API Key" onChange={(e) => setKey(e.target.value)}/>
</div>
<div className="grid gap-2">
<Label htmlFor="email">API Secret</Label>
<Input id="email" type="email" placeholder="API Secret" onChange={(e) => setSecret(e.target.value)}/>
</div>
<div className="grid gap-2">
<Label htmlFor="email">Access Token</Label>
<Input id="email" type="email" placeholder="Access Token" onChange={(e) => setToken(e.target.value)} />
</div>
<div className="grid gap-2">
<Label htmlFor="email">Access Secret</Label>
<Input id="email" type="email" placeholder="Access Secret" onChange={(e) => setTokenSecret(e.target.value)}/>
</div>
<div className="grid gap-2">
<Label htmlFor="email">Tweet</Label>
<Input id="email" type="email" placeholder="Tweet your tweet here" onChange={(e) => setTweet(e.target.value)}/>
</div>
{DatePickerDemo(setDate, date)}
<input aria-label="Time" type="time" onChange={(e) => setTime(e.target.value)}/>
</CardContent>
<CardFooter>
<Button className="w-full" onClick={() => sendTweet({
key,
token,
tokenSecret,
date,
time,
tweet,
secret,
})}>Schedule tweet</Button>
</CardFooter>
</Card>
)
}
Создайте файл под названием src/app/date.tsx
.
"use client"
import * as React from "react"
import { CalendarIcon } from "@radix-ui/react-icons"
import { format } from "date-fns"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
export function DatePickerDemo(onChange: any, date: any) {
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant={"outline"}
className={cn(
"w-[240px] justify-start text-left font-normal",
!date && "text-muted-foreground"
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? format(date, "PPP") : <span>Pick a date</span>}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="single"
selected={date}
onSelect={onChange}
initialFocus
/>
</PopoverContent>
</Popover>
)
}
И разверните фронтенд на Vercel. Ура, ваше приложение заработало!