- {ordered.map(({ website_id, name, domain }, index) =>
+ {ordered.map(({ website_id, name, favicon }, index) =>
index < limit ? (
diff --git a/components/settings/WebsiteSettings.js b/components/settings/WebsiteSettings.js
index 451be47f..22dfc1ad 100644
--- a/components/settings/WebsiteSettings.js
+++ b/components/settings/WebsiteSettings.js
@@ -83,13 +83,13 @@ export default function WebsiteSettings() {
);
- const DetailsLink = ({ website_id, name, domain }) => (
+ const DetailsLink = ({ website_id, name, favicon }) => (
-
+
{name}
);
diff --git a/db/postgresql/migrations/04_add_favicon_column/migration.sql b/db/postgresql/migrations/04_add_favicon_column/migration.sql
new file mode 100644
index 00000000..a1f113ca
--- /dev/null
+++ b/db/postgresql/migrations/04_add_favicon_column/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "website" ADD COLUMN "favicon" VARCHAR(500);
diff --git a/db/postgresql/schema.prisma b/db/postgresql/schema.prisma
index a76a3da4..7c661d2b 100644
--- a/db/postgresql/schema.prisma
+++ b/db/postgresql/schema.prisma
@@ -85,6 +85,7 @@ model website {
domain String? @db.VarChar(500)
share_id String? @unique @db.VarChar(64)
created_at DateTime? @default(now()) @db.Timestamptz(6)
+ favicon String? @db.VarChar(500)
account account @relation(fields: [user_id], references: [user_id])
event event[]
pageview pageview[]
diff --git a/package.json b/package.json
index be7fd7d1..0019e2e0 100644
--- a/package.json
+++ b/package.json
@@ -71,6 +71,7 @@
"del": "^6.0.0",
"detect-browser": "^5.2.0",
"dotenv": "^10.0.0",
+ "favecon": "^1.0.2",
"formik": "^2.2.9",
"fs-extra": "^10.0.1",
"immer": "^9.0.12",
diff --git a/pages/api/website/index.js b/pages/api/website/index.js
index ac02de85..9f57b9a8 100644
--- a/pages/api/website/index.js
+++ b/pages/api/website/index.js
@@ -2,6 +2,22 @@ import { ok, unauthorized, methodNotAllowed, getRandomChars } from 'next-basics'
import { updateWebsite, createWebsite, getWebsiteById } from 'queries';
import { useAuth } from 'lib/middleware';
import { uuid } from 'lib/crypto';
+import favecon from 'favecon';
+
+const getFavicon = async domain => {
+ try {
+ const icons = await favecon.getIcons(`https://${domain}`);
+
+ if (icons.length && icons.length > 0) {
+ return icons[0]?.href;
+ }
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.log(`Could not fetch favicon for domain ${domain}`, e);
+ }
+
+ return null;
+};
export default async (req, res) => {
await useAuth(req, res);
@@ -13,6 +29,8 @@ export default async (req, res) => {
const { name, domain, owner } = req.body;
const website_owner = parseInt(owner);
+ const favicon = await getFavicon(domain);
+
if (website_id) {
const website = await getWebsiteById(website_id);
@@ -28,13 +46,19 @@ export default async (req, res) => {
share_id = null;
}
- await updateWebsite(website_id, { name, domain, share_id, user_id: website_owner });
+ await updateWebsite(website_id, { name, domain, share_id, user_id: website_owner, favicon });
return ok(res);
} else {
const website_uuid = uuid();
const share_id = enable_share_url ? getRandomChars(8) : null;
- const website = await createWebsite(website_owner, { website_uuid, name, domain, share_id });
+ const website = await createWebsite(website_owner, {
+ website_uuid,
+ name,
+ domain,
+ share_id,
+ favicon,
+ });
return ok(res, website);
}
diff --git a/public/default-favicon.png b/public/default-favicon.png
new file mode 100644
index 00000000..b018b02c
Binary files /dev/null and b/public/default-favicon.png differ
diff --git a/yarn.lock b/yarn.lock
index f4b49dc1..19728cf0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3451,6 +3451,14 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
+favecon@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/favecon/-/favecon-1.0.2.tgz#450ae94066900be474b9e408799ecf6e4a65658c"
+ integrity sha512-2jpgF7sMheI/UBGJLzi4JyknC26Bly1txL3rCNNxsY9yHp4lflB3kEYX79zZ/JeSqmQAf8tbktqonVkuj+iwOQ==
+ dependencies:
+ node-fetch "^2.6.1"
+ node-html-parser "^4.0.0"
+
fetch-blob@^3.1.2, fetch-blob@^3.1.4:
version "3.2.0"
resolved "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz"
@@ -3791,6 +3799,11 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
+he@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
+ integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
+
hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2:
version "3.3.2"
resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz"
@@ -4775,6 +4788,13 @@ node-domexception@^1.0.0:
resolved "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz"
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
+node-fetch@^2.6.1:
+ version "2.6.7"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
+ integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
+ dependencies:
+ whatwg-url "^5.0.0"
+
node-fetch@^3.2.8:
version "3.2.10"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-3.2.10.tgz"
@@ -4784,6 +4804,14 @@ node-fetch@^3.2.8:
fetch-blob "^3.1.4"
formdata-polyfill "^4.0.10"
+node-html-parser@^4.0.0:
+ version "4.1.5"
+ resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-4.1.5.tgz#e3ff5b39a098e70de3629c9c79c4e29a3fa5f062"
+ integrity sha512-NLgqUXtftqnBqIjlRjYSaApaqE7TTxfTiH4VqKCjdUJKFOtUzRwney83EHz2qYc0XoxXAkYdmLjENCuZHvsIFg==
+ dependencies:
+ css-select "^4.1.3"
+ he "1.2.0"
+
node-releases@^2.0.3:
version "2.0.5"
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz"
@@ -6475,6 +6503,11 @@ tough-cookie@~2.4.3:
psl "^1.1.24"
punycode "^1.4.1"
+tr46@~0.0.3:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
+ integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
+
trim-newlines@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
@@ -6727,6 +6760,19 @@ web-streams-polyfill@^3.0.3:
resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz"
integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
+webidl-conversions@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
+ integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
+
+whatwg-url@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
+ integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
+ dependencies:
+ tr46 "~0.0.3"
+ webidl-conversions "^3.0.0"
+
which-boxed-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"