Skip to content

Commit

Permalink
feat: add dropbox support
Browse files Browse the repository at this point in the history
  • Loading branch information
Snazzah committed Feb 3, 2024
1 parent 3be86bc commit d1229a8
Show file tree
Hide file tree
Showing 19 changed files with 486 additions and 287 deletions.
3 changes: 2 additions & 1 deletion apps/bot/src/modules/recorder/recording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,8 @@ export default class Recording {
const services: { [key: string]: string } = {
google: 'Google Drive',
dropbox: 'Dropbox',
onedrive: 'OneDrive'
onedrive: 'OneDrive',
box: 'Box'
};
const service = services[user.driveService] ?? user.driveService;

Expand Down
3 changes: 3 additions & 0 deletions apps/dashboard/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,8 @@ GOOGLE_CLIENT_SECRET=
MICROSOFT_CLIENT_ID=
MICROSOFT_CLIENT_SECRET=

DROPBOX_CLIENT_ID=
DROPBOX_CLIENT_SECRET=

APP_URI=http://localhost:3000
JWT_SECRET=1234
17 changes: 17 additions & 0 deletions apps/dashboard/components/dropboxButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
interface ButtonProps {
onClick?(): any;
}

export default function DropboxButton({ onClick }: ButtonProps) {
return (
<button
onClick={onClick}
className="shadow flex items-center justify-center font-roboto transition-colors py-2 px-4 gap-4 text-neutral-500 bg-white rounded active:bg-neutral-200 hover:ring-2 focus:outline-none"
>
<svg xmlns="http://www.w3.org/2000/svg" className="w-6 h-6" viewBox="0 0 256 218">
<path fill="#0061FF" d="M63.995 0L0 40.771l63.995 40.772L128 40.771zM192 0l-64 40.775l64 40.775l64.001-40.775zM0 122.321l63.995 40.772L128 122.321L63.995 81.55zM192 81.55l-64 40.775l64 40.774l64-40.774zM64 176.771l64.005 40.772L192 176.771L128.005 136z"/>

Check warning on line 12 in apps/dashboard/components/dropboxButton.tsx

View workflow job for this annotation

GitHub Actions / Lint source code

Replace `·fill="#0061FF"·d="M63.995·0L0·40.771l63.995·40.772L128·40.771zM192·0l-64·40.775l64·40.775l64.001-40.775zM0·122.321l63.995·40.772L128·122.321L63.995·81.55zM192·81.55l-64·40.775l64·40.774l64-40.774zM64·176.771l64.005·40.772L192·176.771L128.005·136z"` with `⏎··········fill="#0061FF"⏎··········d="M63.995·0L0·40.771l63.995·40.772L128·40.771zM192·0l-64·40.775l64·40.775l64.001-40.775zM0·122.321l63.995·40.772L128·122.321L63.995·81.55zM192·81.55l-64·40.775l64·40.774l64-40.774zM64·176.771l64.005·40.772L192·176.771L128.005·136z"⏎········`
</svg>
<span className="font-semibold">Sign in with Dropbox</span>
</button>
);
}
2 changes: 1 addition & 1 deletion apps/dashboard/components/selectableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface SelectableRowProps {
export default function SelectableRow({ title, children, icon, selected, disabled, hidden, onClick }: SelectableRowProps) {
return (
<div className="flex justify-between bg-zinc-600 rounded-md px-3 py-2 shadow-md w-full">
<div className="flex group justify-center items-center gap-2 font-medium select-none" onClick={!disabled ? onClick : undefined}>
<div className="flex group justify-center items-center gap-2 font-medium select-none" onClick={!disabled && !hidden ? onClick : undefined}>
{!hidden && (
<svg
viewBox="0 0 24 24"
Expand Down
7 changes: 7 additions & 0 deletions apps/dashboard/components/svg/dropbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function DropboxLogo(props) {
return (
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 218" {...props}>
<path fill="#0061FF" d="M63.995 0L0 40.771l63.995 40.772L128 40.771zM192 0l-64 40.775l64 40.775l64.001-40.775zM0 122.321l63.995 40.772L128 122.321L63.995 81.55zM192 81.55l-64 40.775l64 40.774l64-40.774zM64 176.771l64.005 40.772L192 176.771L128.005 136z"/>

Check warning on line 4 in apps/dashboard/components/svg/dropbox.tsx

View workflow job for this annotation

GitHub Actions / Lint source code

Replace `·fill="#0061FF"·d="M63.995·0L0·40.771l63.995·40.772L128·40.771zM192·0l-64·40.775l64·40.775l64.001-40.775zM0·122.321l63.995·40.772L128·122.321L63.995·81.55zM192·81.55l-64·40.775l64·40.774l64-40.774zM64·176.771l64.005·40.772L192·176.771L128.005·136z"` with `⏎········fill="#0061FF"⏎········d="M63.995·0L0·40.771l63.995·40.772L128·40.771zM192·0l-64·40.775l64·40.775l64.001-40.775zM0·122.321l63.995·40.772L128·122.321L63.995·81.55zM192·81.55l-64·40.775l64·40.774l64-40.774zM64·176.771l64.005·40.772L192·176.771L128.005·136z"⏎······`
</svg>
);
}
1 change: 1 addition & 0 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"autoprefixer": "^10.4.12",
"clsx": "^1.2.1",
"cookie": "^0.5.0",
"dropbox": "^10.34.0",
"googleapis": "^104.0.0",
"jsonwebtoken": "^8.5.1",
"next": "12.1.6",
Expand Down
18 changes: 18 additions & 0 deletions apps/dashboard/pages/api/dropbox/disconnect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { NextApiRequest, NextApiResponse } from 'next';

import prisma from '../../../lib/prisma';
import { parseUser } from '../../../utils';
import { setNextAvailableService } from '../../../utils/prisma';

export default async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method !== 'GET') return res.redirect('/');
const user = parseUser(req);
if (!user) return res.redirect('/');

const dbUser = await prisma.user.findUnique({ where: { id: user.id } });
await setNextAvailableService(dbUser, 'dropbox');

await prisma.dropboxUser.delete({ where: { id: user.id } });

res.redirect('/?r=dropbox_unlinked');
};
61 changes: 61 additions & 0 deletions apps/dashboard/pages/api/dropbox/oauth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Dropbox, DropboxAuth } from 'dropbox';
import { NextApiRequest, NextApiResponse } from 'next';

import prisma from '../../../lib/prisma';
import { parseUser } from '../../../utils';
import { config } from '../../../utils/config';

const REDIRECT_URI = `${config.appUri}/api/dropbox/oauth`;
const scopes = ['account_info.read', 'files.content.write'];
const auth = new DropboxAuth({
clientId: config.dropboxClientId,
clientSecret: config.dropboxClientSecret
});

export default async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method !== 'GET') return res.redirect('/');
const user = parseUser(req);
if (!user) return res.redirect('/');
const dbUser = await prisma.user.findFirst({ where: { id: user.id } });
if (!dbUser) return res.redirect('/');
if (dbUser.rewardTier === 0) return res.redirect('/');

const { code = null, error = null } = req.query;
if (error) return res.redirect(`/?error=${req.query.error}&from=dropbox`);

if (!code || typeof code !== 'string')
return res.redirect((await auth.getAuthenticationUrl(REDIRECT_URI, null, 'code', 'offline', scopes, 'none', false)) as string);

try {
const response = await auth.getAccessTokenFromCode(REDIRECT_URI, code);
const tokens: { access_token: string; refresh_token: string; scope: string } = response.result as any;
const scopesRecieved = tokens.scope.split(' ');

if (scopes.find((s) => !scopesRecieved.includes(s))) return res.redirect(`/?error=invalid_scope&from=dropbox`);

const dbx = new Dropbox({
accessToken: tokens.access_token,
clientId: config.dropboxClientId,
clientSecret: config.dropboxClientSecret
});

const dropboxUser = await dbx.usersGetCurrentAccount();

await prisma.dropboxUser.upsert({
where: { id: user.id },
update: { token: tokens.access_token, refreshToken: tokens.refresh_token, name: dropboxUser.result.name.display_name },
create: { id: user.id, token: tokens.access_token, refreshToken: tokens.refresh_token, name: dropboxUser.result.name.display_name }
});
} catch (e) {
console.log(e)

Check warning on line 50 in apps/dashboard/pages/api/dropbox/oauth.ts

View workflow job for this annotation

GitHub Actions / Lint source code

Insert `;`
return res.redirect(`/?error=${encodeURIComponent('Could not get user data, please sign in again.')}&from=dropbox`);
}

await prisma.user.upsert({
where: { id: user.id },
update: { driveService: 'dropbox' },
create: { id: user.id, driveService: 'dropbox' }
});

res.redirect('/?r=dropbox_linked');
};
2 changes: 1 addition & 1 deletion apps/dashboard/pages/api/user/drive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { parseUser } from '../../../utils';

const formats = ['flac', 'aac', 'oggflac', 'heaac', 'opus', 'vorbis', 'adpcm', 'wav8'];
const containers = ['aupzip', 'zip', 'mix'];
const services = ['google', 'onedrive'];
const services = ['google', 'onedrive', 'dropbox'];

export default async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method !== 'PUT') return res.status(405).send({ error: 'Method not allowed' });
Expand Down
32 changes: 31 additions & 1 deletion apps/dashboard/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import { Modal } from '../components/modal';
import Row from '../components/row';
import Section from '../components/section';
import SelectableRow from '../components/selectableRow';
import DropboxLogo from '../components/svg/dropbox';
import GoogleDriveLogo from '../components/svg/googleDrive';
import OneDriveLogo from '../components/svg/oneDrive';
import PatreonLogo from '../components/svg/patreon';
import Toggle from '../components/toggle';
import prisma from '../lib/prisma';
import { getAvatarUrl, parseUser } from '../utils';
import { DiscordUser } from '../utils/types';
import DropboxButton from '../components/dropboxButton';

interface Props {
user: DiscordUser;
Expand All @@ -29,6 +31,7 @@ interface Props {
drive: DriveProps;
googleDrive: boolean;
microsoft: boolean;
dropbox: boolean;
}

interface DriveProps {
Expand Down Expand Up @@ -148,6 +151,7 @@ export default function Index(props: Props) {
else if (from === 'patreon') title = 'An error occurred while connecting to Patreon.';
else if (from === 'discord') title = 'An error occurred while connecting to Discord.';
else if (from === 'microsoft') title = 'An error occurred while connecting to Microsoft.';
else if (from === 'dropbox') title = 'An error occurred while connecting to Dropbox.';
else title = 'An error occurred.';

if (error === 'access_denied') content = 'You denied access to your account.';
Expand Down Expand Up @@ -180,6 +184,14 @@ export default function Index(props: Props) {
<Link href="https://microsoft.com/consent">Microsoft settings</Link>.
</span>
);
} else if (r === 'dropbox_unlinked') {
title = 'Dropbox unlinked.';
content = (
<span>
You have unlinked your Dropbox account, but you can revoke app permissions in your{' '}
<Link href="https://www.dropbox.com/account/connected_apps">Dropbox settings</Link>.
</span>
);
}

if (title && content) {
Expand Down Expand Up @@ -363,6 +375,22 @@ export default function Index(props: Props) {
<MicrosoftButton onClick={() => (location.href = '/api/microsoft/oauth')} />
)}
</SelectableRow>
<SelectableRow
title="Dropbox"
icon={<DropboxLogo className="w-8 h-8" />}
selected={drive.service === 'dropbox'}
disabled={loading}
hidden={!props.dropbox}
onClick={() => setDriveService('dropbox')}
>
{props.dropbox ? (
<Button type="transparent" className="text-red-500" onClick={() => (location.href = '/api/dropbox/disconnect')}>
Disconnect
</Button>
) : (
<DropboxButton onClick={() => (location.href = '/api/dropbox/oauth')} />
)}
</SelectableRow>
<Dropdown
disabled={loading}
items={formats}
Expand Down Expand Up @@ -429,6 +457,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async function (ctx
const patron = dbUser && dbUser.patronId ? await prisma.patreon.findUnique({ where: { id: dbUser.patronId } }) : null;
const googleDrive = await prisma.googleDriveUser.findUnique({ where: { id: user.id } });
const microsoft = await prisma.microsoftUser.findUnique({ where: { id: user.id } });
const dropbox = await prisma.dropboxUser.findUnique({ where: { id: user.id } });

return {
props: {
Expand All @@ -443,7 +472,8 @@ export const getServerSideProps: GetServerSideProps<Props> = async function (ctx
container: dbUser?.driveContainer || 'zip'
},
googleDrive: !!googleDrive,
microsoft: !!microsoft
microsoft: !!microsoft,
dropbox: !!dropbox
}
};
};
2 changes: 2 additions & 0 deletions apps/dashboard/utils/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export const config = {
googleClientSecret: validateEnv('GOOGLE_CLIENT_SECRET'),
microsoftClientId: validateEnv('MICROSOFT_CLIENT_ID'),
microsoftClientSecret: validateEnv('MICROSOFT_CLIENT_SECRET'),
dropboxClientId: validateEnv('DROPBOX_CLIENT_ID'),
dropboxClientSecret: validateEnv('DROPBOX_CLIENT_SECRET'),
appUri: validateEnv('APP_URI', 'http://localhost:3000'),
jwtSecret: validateEnv('JWT_SECRET', 'this is a development value that should be changed in production!!!!!')
} as const;
5 changes: 4 additions & 1 deletion apps/dashboard/utils/prisma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { User } from '@prisma/client';
import prisma from '../lib/prisma';

export async function setNextAvailableService(user: User, exclude: string) {
const services = ['google', 'onedrive'].filter((s) => s !== exclude);
const services = ['google', 'onedrive', 'dropbox'].filter((s) => s !== exclude);

for (const service of services) {
let serviceData;
Expand All @@ -14,6 +14,9 @@ export async function setNextAvailableService(user: User, exclude: string) {
case 'onedrive':
serviceData = await prisma.microsoftUser.findUnique({ where: { id: user.id } });
break;
case 'dropbox':
serviceData = await prisma.dropboxUser.findUnique({ where: { id: user.id } });
break;
}

if (serviceData) {
Expand Down
2 changes: 1 addition & 1 deletion apps/download/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"clsx": "^1.2.1",
"eslint-config-preact": "^1.3.0",
"i18next": "^21.10.0",
"node-sass": "^7.0.3",
"node-sass": "^9.0.0",
"parse-ms": "^3.0.0",
"postcss": "^8.4.14",
"preact": "^10.9.0",
Expand Down
22 changes: 15 additions & 7 deletions apps/tasks/config/_default.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,26 @@ module.exports = {
// keyPrefix: 'craig:'
// },

// For the drive upload query
// For drive upload in Google Drive
drive: {
clientId: '',
clientSecret: ''
},

// For drive upload in Microsoft OneDrive
microsoft: {
clientId: '',
clientSecret: '',
redirect: ''
},

// For drive upload in Dropbox
dropbox: {
clientId: '',
clientSecret: '',
folderName: 'CraigChat'
},

// for refresh patrons job
patreon: {
campaignId: 0,
Expand All @@ -21,12 +35,6 @@ module.exports = {
skipUsers: []
},

microsoft: {
clientId: '',
clientSecret: '',
redirect: ''
},

downloads: {
expiration: 24 * 60 * 60 * 1000,
path: '../download/downloads'
Expand Down
1 change: 1 addition & 0 deletions apps/tasks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"config": "^3.3.8",
"cron": "^2.1.0",
"dayjs": "^1.11.6",
"dropbox": "^10.34.0",
"googleapis": "^104.0.0",
"lodash.isequal": "^4.5.0",
"winston": "^3.11.0",
Expand Down
Loading

0 comments on commit d1229a8

Please sign in to comment.