Skip to content

Commit

Permalink
Merge pull request #138 from thanhdanh27600/staging
Browse files Browse the repository at this point in the history
Staging
  • Loading branch information
thanhdanh27600 authored Aug 29, 2023
2 parents 90bbaa6 + 22617ec commit 66349ae
Show file tree
Hide file tree
Showing 39 changed files with 530 additions and 123 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ DATABASE_URL="postgres://"
REDIS_AUTH="xxxxxx"
# NEXT_PUBLIC_MIX_PANEL_TOKEN="xxxxxx"
NEXT_PUBLIC_PLATFORM_AUTH="xxxxxx"
NEXT_PUBLIC_SERVER_AUTH="xxxxxx"
SERVER_AUTH="xxxxxx"
Binary file modified .env.production.gpg
Binary file not shown.
Binary file modified .env.test.gpg
Binary file not shown.
2 changes: 1 addition & 1 deletion .github/workflows/cd-short.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Decrypt secret
run: ./scripts/decrypt_secret.sh .env.production.gpg
run: ./scripts/decrypt_secret.sh .env.production.gpg ./.env
env:
SECRET_PASSPHRASE: ${{ secrets.SECRET_PASSPHRASE }}
- name: Login to GitHub Container Registry
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Decrypt secret
run: ./scripts/decrypt_secret.sh .env.production.gpg
run: ./scripts/decrypt_secret.sh .env.production.gpg ./.env
env:
SECRET_PASSPHRASE: ${{ secrets.SECRET_PASSPHRASE }}
- name: Login to GitHub Container Registry
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
with:
node-version: 16.x
- name: Decrypt secret
run: ./scripts/decrypt_secret.sh .env.test.gpg
run: ./scripts/decrypt_secret.sh .env.test.gpg ./.env.test
env:
SECRET_PASSPHRASE: ${{ secrets.SECRET_PASSPHRASE }}
- run: npm i
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@types/ramda": "^0.29.3",
"add": "^2.0.6",
"axios": "^1.2.6",
"cloudinary": "^1.40.0",
"clsx": "^1.2.1",
"crypto-js": "^4.1.1",
"dayjs": "^1.11.9",
Expand All @@ -38,6 +39,7 @@
"jest": "^29.5.0",
"mixpanel-browser": "^2.45.0",
"next": "13.1.6",
"next-cloudinary": "^4.19.0",
"next-i18next": "^14.0.0",
"node-fetch": "^3.3.0",
"node-mocks-http": "^1.12.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- CreateTable
CREATE TABLE "Media" (
"id" SERIAL NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"url" TEXT NOT NULL,
"urlShortenerHistoryId" INTEGER,

CONSTRAINT "Media_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "Media_url_key" ON "Media"("url");

-- CreateIndex
CREATE UNIQUE INDEX "Media_urlShortenerHistoryId_key" ON "Media"("urlShortenerHistoryId");

-- AddForeignKey
ALTER TABLE "Media" ADD CONSTRAINT "Media_urlShortenerHistoryId_fkey" FOREIGN KEY ("urlShortenerHistoryId") REFERENCES "UrlShortenerHistory"("id") ON DELETE CASCADE ON UPDATE CASCADE;
13 changes: 12 additions & 1 deletion prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ model UrlShortenerHistory {
ogDescription String?
ogImgSrc String?
UrlShortenerRecord UrlShortenerRecord @relation(fields: [urlShortenerRecordId], references: [id], onDelete: Cascade)
UrlForwardMeta UrlForwardMeta[]
urlShortenerRecordId Int
urlForwardMeta UrlForwardMeta[]
Media Media?
}

model UrlForwardMeta {
Expand All @@ -51,3 +52,13 @@ model UrlForwardMeta {
@@unique([userAgent, ip, urlShortenerHistoryId])
}

model Media {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
url String @unique
urlShortenerHistoryId Int? @unique
UrlShortenerHistory UrlShortenerHistory? @relation(fields: [urlShortenerHistoryId], references: [id], onDelete: Cascade)
}
5 changes: 4 additions & 1 deletion public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,8 @@
"loadMore": "Load more",
"editPreview": "More options",
"save": "Save",
"maximumCharaters": "Maximum of {{n}} characters"
"maximumCharaters": "Maximum of {{n}} characters",
"uploadImage": "Upload image",
"title": "Title",
"description": "Description"
}
5 changes: 4 additions & 1 deletion public/locales/vi/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,8 @@
"loadMore": "Xem thêm",
"editPreview": "Chỉnh sửa",
"save": "Lưu thay đổi",
"maximumCharaters": "Tối đa {{n}} ký tự"
"maximumCharaters": "Tối đa {{n}} ký tự",
"uploadImage": "Upload ảnh",
"title": "Tiêu đề",
"description": "Mô tả ngắn"
}
6 changes: 3 additions & 3 deletions scripts/decrypt_secret.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
#!/bin/sh

if [ -z "$1" ]
if [ -z "$1" ] || [ -z "$2" ]
then
echo "Decript failed. Host is empty!"
else
echo "Decript with host: $1...";
echo "Decript with host: $1, to $2...";
# --batch to prevent interactive command
# --yes to assume "yes" for questions
gpg --quiet --batch --yes --decrypt --passphrase="$SECRET_PASSPHRASE" \
--output ./.env $1
--output $2 $1
echo "Decript successfully";
fi
6 changes: 6 additions & 0 deletions src/api/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,24 @@ export const updateShortenUrlRequest = async ({
hash,
ogTitle,
ogDescription,
ogImgSrc,
mediaId,
locale,
}: {
hash: string;
ogDescription?: string;
ogTitle?: string;
ogImgSrc?: string;
mediaId?: number;
locale: Locale;
}) => {
const rs = await API.put(`/api/shorten/update`, {
locale,
hash,
ogTitle,
ogDescription,
ogImgSrc,
mediaId,
});
const data = rs.data;
return data as ShortenUrl;
Expand Down
4 changes: 4 additions & 0 deletions src/bear/shortenSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,20 @@ export interface ShortenSlice {
shortenHistoryForm?: Partial<UrlShortenerHistory>;
setShortenHistoryForm: (history?: Partial<UrlShortenerHistory>) => void;
getShortenUrl: () => string;
shortenHistoryMediaId?: number;
setShortenHistoryMediaId: (id: number) => void;
}

const slice: StateCreator<ShortenSlice> = (set, get) => ({
shortenHistory: undefined,
shortenHistoryForm: undefined,
shortenHistoryMediaId: undefined,
getShortenUrl: () => (!!get().shortenHistory ? `${BASE_URL_SHORT}/${get().shortenHistory?.hash}` : ''),

setShortenHistory: (history) => set((state) => ({ shortenHistory: { ...state.shortenHistory, ...history } })),
setShortenHistoryForm: (history) =>
set((state) => ({ shortenHistoryForm: { ...state.shortenHistoryForm, ...history } })),
setShortenHistoryMediaId: (id: number) => set({ shortenHistoryMediaId: id }),
});

const shortenSlice = create(withDevTools(slice, { anonymousActionType: 'ShortenSlice' }));
Expand Down
2 changes: 1 addition & 1 deletion src/components/atoms/Accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const Accordion = ({ children, title }: Props) => {
className="!visible hidden border-0"
data-te-collapse-item
aria-labelledby={`heading${id}`}>
<div>{children}</div>
{children}
</div>
</div>
</div>
Expand Down
61 changes: 61 additions & 0 deletions src/components/atoms/UploadImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { UploadCloud } from '@styled-icons/feather';
import { API } from 'api/axios';
import { CldUploadWidget } from 'next-cloudinary';
import { MouseEventHandler, useState } from 'react';
import { isProduction } from 'types/constants';

export const UploadImage = ({
onSuccess,
}: {
onSuccess: ({ url, mediaId }: { url: string; mediaId: number }) => void;
}) => {
const [resource, setResource] = useState<any>();

return (
<div>
<CldUploadWidget
options={{
maxFileSize: 5e6,
maxImageHeight: 2400,
croppingAspectRatio: 1200 / 630,
cropping: true,
multiple: false,
showSkipCropButton: false,
singleUploadAutoClose: true,
showPoweredBy: false,
showUploadMoreButton: false,
maxFiles: 1,
}}
signatureEndpoint="/api/cld"
uploadPreset="clickdi"
onSuccess={async (result, widget) => {
setResource(result?.info);
const url = (result?.info as any)?.secure_url;
const rs = await API.post('/api/i', { url });
if (url && rs) {
onSuccess({ url, mediaId: rs.data.id });
}
widget.close();
}}>
{({ open }) => {
const handleOnClick: MouseEventHandler<HTMLButtonElement> = (e) => {
e.preventDefault();
open();
};
return (
<button
className="w-fit cursor-pointer rounded-lg border border-gray-300 p-4 transition-colors hover:bg-gray-100"
onClick={handleOnClick}>
<UploadCloud className="h-8 w-8 text-gray-700" />
</button>
);
}}
</CldUploadWidget>
{!isProduction && (
<a className="mt-2 block" href={resource?.secure_url} target="_blank">
URL (debug): {resource?.secure_url.slice(-20) || '--'}
</a>
)}
</div>
);
};
74 changes: 50 additions & 24 deletions src/components/gadgets/AdvancedSettingUrlForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useBearStore } from 'bear';
import { Button } from 'components/atoms/Button';
import { Input, Textarea } from 'components/atoms/Input';
import mixpanel from 'mixpanel-browser';
import dynamic from 'next/dynamic';
import { useCallback, useEffect } from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
Expand All @@ -19,13 +20,19 @@ import { QueryKey } from 'utils/requests';

type ShortenSettingPayload = Partial<UrlShortenerHistory> & { locale?: Locale };

const UploadImage = dynamic(() => import('../atoms/UploadImage').then((mod) => mod.UploadImage));

export const AdvancedSettingUrlForm = () => {
const { t, locale } = useTrans();
const { shortenSlice } = useBearStore();
const [shortenHistory, setShortenHistoryForm] = shortenSlice((state) => [
state.shortenHistory,
state.setShortenHistoryForm,
]);
const [shortenHistory, shortenHistoryMediaId, setShortenHistoryForm, setShortenHistoryMediaId] = shortenSlice(
(state) => [
state.shortenHistory,
state.shortenHistoryMediaId,
state.setShortenHistoryForm,
state.setShortenHistoryMediaId,
],
);
const defaultValues = {
ogTitle: shortenHistory?.ogTitle || t('ogTitle', { hash: shortenHistory?.hash ?? 'XXX' }),
ogDescription: shortenHistory?.ogDescription || t('ogDescription'),
Expand All @@ -35,18 +42,25 @@ export const AdvancedSettingUrlForm = () => {

const {
register,
setValue,
handleSubmit,
formState: { errors },
watch,
} = useForm<ShortenSettingPayload>({
defaultValues,
});

const onUpdateImgSrc = ({ url, mediaId }: any) => {
setValue('ogImgSrc', url);
setShortenHistoryMediaId(mediaId);
setShortenHistoryForm({ ogImgSrc: url });
};

const handleUpdate = useCallback((history: ShortenSettingPayload) => {
setShortenHistoryForm(history);
}, []);

const debouncedUpdate = useCallback(debounce(handleUpdate, 1000), []);
const debouncedUpdate = useCallback(debounce(handleUpdate, 600), []);

const [ogTitle, ogDescription] = watch(['ogTitle', 'ogDescription']);

Expand All @@ -60,8 +74,9 @@ export const AdvancedSettingUrlForm = () => {
useEffect(() => {
if (shortenHistory)
debouncedUpdate({
ogTitle: shortenHistory.ogDescription || t('ogTitle', { hash: shortenHistory.hash ?? 'XXX' }),
ogTitle: shortenHistory.ogTitle || t('ogTitle', { hash: shortenHistory.hash ?? 'XXX' }),
ogDescription: shortenHistory.ogDescription || t('ogDescription'),
ogImgSrc: shortenHistory.ogImgSrc,
});
}, [shortenHistory]);

Expand All @@ -87,8 +102,10 @@ export const AdvancedSettingUrlForm = () => {
updateShortenUrl.mutate({
hash: shortenHistory.hash,
locale,
ogDescription: values.ogDescription || undefined,
ogTitle: values.ogTitle || undefined,
mediaId: shortenHistoryMediaId || undefined,
ogImgSrc: values.ogImgSrc || undefined,
ogDescription: values.ogDescription?.trim() || undefined,
ogTitle: values.ogTitle?.trim() || undefined,
});
};

Expand All @@ -98,24 +115,33 @@ export const AdvancedSettingUrlForm = () => {
<form onSubmit={handleSubmit(onSubmit)}>
<div className="mt-4">
<div>
<label>Title</label>
<Input
btnSize="md"
{...register('ogTitle', {
required: { message: t('errorNoInput'), value: true },
maxLength: { message: t('maximumCharaters', { n: LIMIT_OG_TITLE_LENGTH }), value: LIMIT_OG_TITLE_LENGTH },
})}
maxLength={LIMIT_OG_TITLE_LENGTH}
disabled={updateShortenUrl.isLoading}
/>
<ErrorMessage
errors={errors}
name="ogTitle"
render={(error) => <p className="text-red-400">{error.message}</p>}
/>
<label>{t('uploadImage')}</label>
<div className="mt-2">
<UploadImage onSuccess={onUpdateImgSrc} />
</div>
<div className="mt-4">
<label>{t('title')}</label>
<Input
btnSize="md"
{...register('ogTitle', {
required: { message: t('errorNoInput'), value: true },
maxLength: {
message: t('maximumCharaters', { n: LIMIT_OG_TITLE_LENGTH }),
value: LIMIT_OG_TITLE_LENGTH,
},
})}
maxLength={LIMIT_OG_TITLE_LENGTH}
disabled={updateShortenUrl.isLoading}
/>
<ErrorMessage
errors={errors}
name="ogTitle"
render={(error) => <p className="text-red-400">{error.message}</p>}
/>
</div>
</div>
<div className="mt-4">
<label>Description</label>
<label>{t('description')}</label>
<Textarea
{...register('ogDescription', {
required: { message: t('errorNoInput'), value: true },
Expand Down
Loading

0 comments on commit 66349ae

Please sign in to comment.