Создание планировщика Twitter с помощью React и Hasura
В этом руководстве вы узнаете, как создать планировщик твитов. Введите свои учетные данные Twitter, выберите время и дату, и ваш твит будет опубликован в это время.
Rocketgraph: Замена Firebase с открытым исходным кодом
Краткая информация о нас. Rocketgraph позволяет разработчикам создавать веб-приложения за считанные минуты.
Для этого мы предоставляем:
- PostgresDB, размещенный на AWS RDS.
- Консоль Hasura для GraphQL-анализа PostgresDB, управления разрешениями, кронами, планировщиками и многим другим.
- Встроенная аутентификация, поддерживающая электронную почту/пароль, социальную сеть, волшебную ссылку и одноразовый пароль.
- Бессерверные функции для любого дополнительного кода бэкенда.
Прежде чем мы начнём!
Мы будем использовать планировщики включения-выключения Hasura для запуска Twitter API в нужное время и пограничные функции Rocketgraph для настройки бэкенда. Итак, нам нужно создать проект на Rocketgraph. Это бесплатно.
Регистрация
Создание проекта
Это позволит настроить консоль 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. Ура, ваше приложение заработало!