feat: Serve static assets of apps
This commit is contained in:
@@ -6,6 +6,7 @@ import corsOptions from './config/cors-options';
|
|||||||
import graphQLInstance from './helpers/graphql-instance';
|
import graphQLInstance from './helpers/graphql-instance';
|
||||||
import logger from './helpers/logger';
|
import logger from './helpers/logger';
|
||||||
import morgan from './helpers/morgan';
|
import morgan from './helpers/morgan';
|
||||||
|
import appAssetsHandler from './helpers/app-assets-handler';
|
||||||
import errorHandler from './helpers/error-handler';
|
import errorHandler from './helpers/error-handler';
|
||||||
import './config/database';
|
import './config/database';
|
||||||
import authentication from './helpers/authentication';
|
import authentication from './helpers/authentication';
|
||||||
@@ -13,6 +14,8 @@ import authentication from './helpers/authentication';
|
|||||||
const app = express();
|
const app = express();
|
||||||
const port = appConfig.port;
|
const port = appConfig.port;
|
||||||
|
|
||||||
|
appAssetsHandler(app)
|
||||||
|
|
||||||
app.use(morgan);
|
app.use(morgan);
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.urlencoded({ extended: false }));
|
app.use(express.urlencoded({ extended: false }));
|
||||||
|
5
packages/backend/src/apps/flickr/assets/favicon.svg
Normal file
5
packages/backend/src/apps/flickr/assets/favicon.svg
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" aria-label="Flickr" role="img" viewBox="0 0 512 512">
|
||||||
|
<rect width="512" height="512" rx="15%" fill="#fff"/>
|
||||||
|
<circle cx="157" cy="256" fill="#0063dc" r="79"/>
|
||||||
|
<circle cx="355" cy="256" fill="#ff0084" r="79"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 261 B |
@@ -1,7 +1,10 @@
|
|||||||
{
|
import appInfoType from '../../types/app-info';
|
||||||
|
import appConfig from '../../config/app';
|
||||||
|
|
||||||
|
const appInfo: appInfoType = {
|
||||||
"name": "Flickr",
|
"name": "Flickr",
|
||||||
"key": "flickr",
|
"key": "flickr",
|
||||||
"iconUrl": "https://automatisch.io/apps/flickr.png",
|
"iconUrl": `${appConfig.baseUrl}/apps/flickr/assets/favicon.svg`,
|
||||||
"docUrl": "https://automatisch.io/docs/flickr",
|
"docUrl": "https://automatisch.io/docs/flickr",
|
||||||
"primaryColor": "000000",
|
"primaryColor": "000000",
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -113,3 +116,5 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default appInfo;
|
4
packages/backend/src/apps/twitch/assets/favicon.svg
Normal file
4
packages/backend/src/apps/twitch/assets/favicon.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" aria-label="Twitch" role="img" viewBox="0 0 512 512">
|
||||||
|
<rect width="512" height="512" rx="15%" fill="#fff"/>
|
||||||
|
<path d="m115 101-22 56v228h78v42h44l41-42h63l85-85v-199zm260 185-48 48h-78l-42 42v-42h-65v-204h233zm-48-100v85h-30v-85zm-78 0v85h-29v-85z" fill="#6441a4"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 316 B |
@@ -1,7 +1,10 @@
|
|||||||
{
|
import appInfoType from '../../types/app-info';
|
||||||
|
import appConfig from '../../config/app';
|
||||||
|
|
||||||
|
const appInfo: appInfoType = {
|
||||||
"name": "Twitch",
|
"name": "Twitch",
|
||||||
"key": "twitch",
|
"key": "twitch",
|
||||||
"iconUrl": "https://automatisch.io/apps/twitch.png",
|
"iconUrl": `${appConfig.baseUrl}/apps/twitch/assets/favicon.svg`,
|
||||||
"docUrl": "https://automatisch.io/docs/twitch",
|
"docUrl": "https://automatisch.io/docs/twitch",
|
||||||
"primaryColor": "6441a5",
|
"primaryColor": "6441a5",
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -43,3 +46,5 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default appInfo;
|
4
packages/backend/src/apps/twitter/assets/favicon.svg
Normal file
4
packages/backend/src/apps/twitter/assets/favicon.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" aria-label="Twitter" role="img" viewBox="0 0 512 512">
|
||||||
|
<rect width="512" height="512" rx="15%" fill="#1da1f2"/>
|
||||||
|
<path fill="#fff" d="M437 152a72 72 0 01-40 12a72 72 0 0032-40a72 72 0 01-45 17a72 72 0 00-122 65a200 200 0 01-145-74a72 72 0 0022 94a72 72 0 01-32-7a72 72 0 0056 69a72 72 0 01-32 1a72 72 0 0067 50a200 200 0 01-105 29a200 200 0 00309-179a200 200 0 0035-37"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 422 B |
@@ -1,7 +1,10 @@
|
|||||||
{
|
import appInfoType from '../../types/app-info';
|
||||||
|
import appConfig from '../../config/app';
|
||||||
|
|
||||||
|
const appInfo: appInfoType = {
|
||||||
"name": "Twitter",
|
"name": "Twitter",
|
||||||
"key": "twitter",
|
"key": "twitter",
|
||||||
"iconUrl": "https://automatisch.io/apps/twitter.png",
|
"iconUrl": `${appConfig.baseUrl}/apps/twitter/assets/favicon.svg`,
|
||||||
"docUrl": "https://automatisch.io/docs/twitter",
|
"docUrl": "https://automatisch.io/docs/twitter",
|
||||||
"primaryColor": "2DAAE1",
|
"primaryColor": "2DAAE1",
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -113,3 +116,5 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default appInfo;
|
@@ -11,7 +11,9 @@ type AppConfig = {
|
|||||||
postgresPort: number,
|
postgresPort: number,
|
||||||
postgresHost: string,
|
postgresHost: string,
|
||||||
postgresUsername: string,
|
postgresUsername: string,
|
||||||
postgresPassword: string
|
postgresPassword: string,
|
||||||
|
baseUrl?: string
|
||||||
|
webAppUrl?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const appConfig: AppConfig = {
|
const appConfig: AppConfig = {
|
||||||
@@ -27,4 +29,10 @@ const appConfig: AppConfig = {
|
|||||||
postgresPassword: process.env.POSTGRESS_PASSWORD,
|
postgresPassword: process.env.POSTGRESS_PASSWORD,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const webAppUrl = `${appConfig.protocol}://${appConfig.host}:${appConfig.corsPort}`;
|
||||||
|
appConfig.webAppUrl = webAppUrl;
|
||||||
|
|
||||||
|
const baseUrl = `${appConfig.protocol}://${appConfig.host}:${appConfig.port}`;
|
||||||
|
appConfig.baseUrl = baseUrl;
|
||||||
|
|
||||||
export default appConfig;
|
export default appConfig;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import appConfig from './app'
|
import appConfig from './app'
|
||||||
|
|
||||||
const corsOptions = {
|
const corsOptions = {
|
||||||
origin: `${appConfig.protocol}://${appConfig.host}:${appConfig.corsPort}`,
|
origin: appConfig.webAppUrl,
|
||||||
methods: 'POST',
|
methods: 'POST',
|
||||||
credentials: true,
|
credentials: true,
|
||||||
optionsSuccessStatus: 200,
|
optionsSuccessStatus: 200,
|
||||||
|
@@ -9,7 +9,7 @@ type Params = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getConnectedAppsResolver = async (params: Params, req: RequestWithCurrentUser) => {
|
const getConnectedAppsResolver = async (params: Params, req: RequestWithCurrentUser) => {
|
||||||
let apps = App.findAll(params.name)
|
let apps = await App.findAll(params.name)
|
||||||
|
|
||||||
const connections = await Connection.query()
|
const connections = await Connection.query()
|
||||||
.select('connections.key')
|
.select('connections.key')
|
||||||
|
15
packages/backend/src/helpers/app-assets-handler.ts
Normal file
15
packages/backend/src/helpers/app-assets-handler.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import express, { Application } from 'express';
|
||||||
|
import App from '../models/app';
|
||||||
|
|
||||||
|
const appAssetsHandler = async (app: Application) => {
|
||||||
|
const appNames = App.list;
|
||||||
|
|
||||||
|
appNames.forEach(appName => {
|
||||||
|
app.use(
|
||||||
|
`/apps/${appName}/assets/favicon.svg`,
|
||||||
|
express.static(`src/apps/${appName}/assets/favicon.svg`)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default appAssetsHandler;
|
@@ -4,22 +4,28 @@ class App {
|
|||||||
static folderPath = __dirname + '/../apps';
|
static folderPath = __dirname + '/../apps';
|
||||||
static list = fs.readdirSync(this.folderPath);
|
static list = fs.readdirSync(this.folderPath);
|
||||||
|
|
||||||
static findAll(name?: string): object[] {
|
static async findAll(name?: string): Promise<object[]> {
|
||||||
if(!name) return this.list.map((name) => this.findOneByName(name));
|
let appList;
|
||||||
|
|
||||||
return this.list
|
if(!name) {
|
||||||
|
appList = this.list.map(async (name) => await this.findOneByName(name));
|
||||||
|
} else {
|
||||||
|
appList = this.list
|
||||||
.filter((app) => app.includes(name.toLowerCase()))
|
.filter((app) => app.includes(name.toLowerCase()))
|
||||||
.map((name) => this.findOneByName(name));
|
.map(async (name) => await this.findOneByName(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
static findOneByName(name: string): object {
|
return Promise.all(appList)
|
||||||
const rawAppData = fs.readFileSync(this.folderPath + `/${name}/info.json`, 'utf-8');
|
|
||||||
return JSON.parse(rawAppData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static findOneByKey(key: string): object {
|
static async findOneByName(name: string): Promise<object> {
|
||||||
const rawAppData = fs.readFileSync(this.folderPath + `/${key}/info.json`, 'utf-8');
|
const rawAppData = (await import(`../apps/${name}/info`)).default;
|
||||||
return JSON.parse(rawAppData);
|
return rawAppData;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async findOneByKey(key: string): Promise<object> {
|
||||||
|
const rawAppData = (await import(`../apps/${key}/info`)).default;
|
||||||
|
return rawAppData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
14
packages/backend/src/types/app-info.ts
Normal file
14
packages/backend/src/types/app-info.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import FieldType from './field';
|
||||||
|
import AuthenticationStepType from './authentication-step';
|
||||||
|
|
||||||
|
type AppInfo = {
|
||||||
|
name: string,
|
||||||
|
key: string,
|
||||||
|
iconUrl: string,
|
||||||
|
docUrl: string,
|
||||||
|
primaryColor: string,
|
||||||
|
fields: FieldType[],
|
||||||
|
authenticationSteps?: AuthenticationStepType[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AppInfo;
|
10
packages/backend/src/types/authentication-step-field.ts
Normal file
10
packages/backend/src/types/authentication-step-field.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
type AuthenticationStepField = {
|
||||||
|
name: string,
|
||||||
|
value: string | null,
|
||||||
|
fields?: {
|
||||||
|
name: string,
|
||||||
|
value: string | null
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthenticationStepField;
|
10
packages/backend/src/types/authentication-step.ts
Normal file
10
packages/backend/src/types/authentication-step.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import AuthenticationStepField from '../types/authentication-step-field';
|
||||||
|
|
||||||
|
type AuthenticationStep = {
|
||||||
|
step: number,
|
||||||
|
type: string,
|
||||||
|
name: string,
|
||||||
|
fields: AuthenticationStepField[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthenticationStep;
|
2
packages/backend/src/types/field.d.ts
vendored
2
packages/backend/src/types/field.d.ts
vendored
@@ -5,7 +5,7 @@ type Field = {
|
|||||||
required: boolean,
|
required: boolean,
|
||||||
readOnly: boolean,
|
readOnly: boolean,
|
||||||
value: string,
|
value: string,
|
||||||
placeholder: string,
|
placeholder: string | null,
|
||||||
description: string,
|
description: string,
|
||||||
docUrl: string,
|
docUrl: string,
|
||||||
clickToCopy: boolean
|
clickToCopy: boolean
|
||||||
|
@@ -7,7 +7,8 @@ type AppIconProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function AppIcon(props: AppIconProps) {
|
export default function AppIcon(props: AppIconProps) {
|
||||||
const { color, name, url } = props;
|
const { name, url } = props;
|
||||||
|
const color = url ? 'white' : props.color
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Avatar component="span" variant="square" sx={{ bgcolor: `#${color}` }} src={url} alt={name} />
|
<Avatar component="span" variant="square" sx={{ bgcolor: `#${color}` }} src={url} alt={name} />
|
||||||
|
Reference in New Issue
Block a user