Compare commits

...

19 Commits

Author SHA1 Message Date
Daniel a276065288 Update .env.sample 2024-09-15 13:51:09 -04:00
Daniel 1461caf68a Merge pull request #748 from linkwarden/hotfix
bug fix
2024-08-29 12:49:48 -04:00
daniel31x13 e7c7fedf8b bug fix 2024-08-29 12:47:23 -04:00
Daniel 5f4e0d4262 Merge pull request #731 from linkwarden/hotfix
bugs fixed
2024-08-19 23:37:30 -04:00
daniel31x13 c072fed99f bugs fixed 2024-08-19 23:36:28 -04:00
Daniel b4a9f917b5 Merge pull request #728 from linkwarden/hotfix
hotfix
2024-08-19 19:30:26 -04:00
daniel31x13 078e5ba95f minor change 2024-08-19 19:30:01 -04:00
daniel31x13 495509c888 bug fix 2024-08-19 19:25:13 -04:00
Daniel 03b4240b8b Merge pull request #720 from linkwarden/revert-719-feat/customizable-links
Revert "Feat/customizable links"
2024-08-18 14:47:29 -04:00
Daniel 9a3e82470a Revert "Feat/customizable links" 2024-08-18 14:46:52 -04:00
Daniel ee2319996b Merge pull request #719 from linkwarden/feat/customizable-links
Feat/customizable links
2024-08-18 14:46:21 -04:00
daniel31x13 c979adfe69 small improvements 2024-08-18 14:45:40 -04:00
daniel31x13 17d1cb45e3 minor improvement 2024-08-18 13:49:33 -04:00
daniel31x13 c18a5f4162 added details drawer 2024-08-18 02:55:59 -04:00
daniel31x13 a40026040c icon picker component 2024-08-16 23:00:37 -04:00
Daniel f944345745 Merge pull request #708 from linkwarden/dev
bump version
2024-08-16 13:45:28 -04:00
daniel31x13 6b647573f0 bump version 2024-08-16 13:44:53 -04:00
Daniel d81493e021 Merge pull request #707 from linkwarden/dev
bug fix
2024-08-16 13:43:57 -04:00
daniel31x13 03f4523d57 bug fix 2024-08-16 13:42:55 -04:00
8 changed files with 162 additions and 46 deletions
+4 -3
View File
@@ -1,11 +1,12 @@
NEXTAUTH_SECRET=very_sensitive_secret
NEXTAUTH_URL=http://localhost:3000/api/v1/auth NEXTAUTH_URL=http://localhost:3000/api/v1/auth
NEXTAUTH_SECRET=
# Manual installation database settings # Manual installation database settings
DATABASE_URL=postgresql://user:password@localhost:5432/linkwarden # Example: DATABASE_URL=postgresql://user:password@localhost:5432/linkwarden
DATABASE_URL=
# Docker installation database settings # Docker installation database settings
POSTGRES_PASSWORD=super_secret_password POSTGRES_PASSWORD=
# Additional Optional Settings # Additional Optional Settings
PAGINATION_TAKE_COUNT= PAGINATION_TAKE_COUNT=
+24 -16
View File
@@ -1,6 +1,8 @@
import { dropdownTriggerer } from "@/lib/client/utils"; import { dropdownTriggerer } from "@/lib/client/utils";
import React from "react"; import React, { useEffect } from "react";
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import { resetInfiniteQueryPagination } from "@/hooks/store/links";
import { useQueryClient } from "@tanstack/react-query";
type Props = { type Props = {
setSearchFilter: Function; setSearchFilter: Function;
@@ -18,6 +20,7 @@ export default function FilterSearchDropdown({
searchFilter, searchFilter,
}: Props) { }: Props) {
const { t } = useTranslation(); const { t } = useTranslation();
const queryClient = useQueryClient();
return ( return (
<div className="dropdown dropdown-bottom dropdown-end"> <div className="dropdown dropdown-bottom dropdown-end">
@@ -41,9 +44,10 @@ export default function FilterSearchDropdown({
name="search-filter-checkbox" name="search-filter-checkbox"
className="checkbox checkbox-primary" className="checkbox checkbox-primary"
checked={searchFilter.name} checked={searchFilter.name}
onChange={() => onChange={() => {
setSearchFilter({ ...searchFilter, name: !searchFilter.name }) resetInfiniteQueryPagination(queryClient, ["links"]);
} setSearchFilter({ ...searchFilter, name: !searchFilter.name });
}}
/> />
<span className="label-text whitespace-nowrap">{t("name")}</span> <span className="label-text whitespace-nowrap">{t("name")}</span>
</label> </label>
@@ -59,9 +63,10 @@ export default function FilterSearchDropdown({
name="search-filter-checkbox" name="search-filter-checkbox"
className="checkbox checkbox-primary" className="checkbox checkbox-primary"
checked={searchFilter.url} checked={searchFilter.url}
onChange={() => onChange={() => {
setSearchFilter({ ...searchFilter, url: !searchFilter.url }) resetInfiniteQueryPagination(queryClient, ["links"]);
} setSearchFilter({ ...searchFilter, url: !searchFilter.url });
}}
/> />
<span className="label-text whitespace-nowrap">{t("link")}</span> <span className="label-text whitespace-nowrap">{t("link")}</span>
</label> </label>
@@ -77,12 +82,13 @@ export default function FilterSearchDropdown({
name="search-filter-checkbox" name="search-filter-checkbox"
className="checkbox checkbox-primary" className="checkbox checkbox-primary"
checked={searchFilter.description} checked={searchFilter.description}
onChange={() => onChange={() => {
resetInfiniteQueryPagination(queryClient, ["links"]);
setSearchFilter({ setSearchFilter({
...searchFilter, ...searchFilter,
description: !searchFilter.description, description: !searchFilter.description,
}) });
} }}
/> />
<span className="label-text whitespace-nowrap"> <span className="label-text whitespace-nowrap">
{t("description")} {t("description")}
@@ -100,9 +106,10 @@ export default function FilterSearchDropdown({
name="search-filter-checkbox" name="search-filter-checkbox"
className="checkbox checkbox-primary" className="checkbox checkbox-primary"
checked={searchFilter.tags} checked={searchFilter.tags}
onChange={() => onChange={() => {
setSearchFilter({ ...searchFilter, tags: !searchFilter.tags }) resetInfiniteQueryPagination(queryClient, ["links"]);
} setSearchFilter({ ...searchFilter, tags: !searchFilter.tags });
}}
/> />
<span className="label-text whitespace-nowrap">{t("tags")}</span> <span className="label-text whitespace-nowrap">{t("tags")}</span>
</label> </label>
@@ -118,12 +125,13 @@ export default function FilterSearchDropdown({
name="search-filter-checkbox" name="search-filter-checkbox"
className="checkbox checkbox-primary" className="checkbox checkbox-primary"
checked={searchFilter.textContent} checked={searchFilter.textContent}
onChange={() => onChange={() => {
resetInfiniteQueryPagination(queryClient, ["links"]);
setSearchFilter({ setSearchFilter({
...searchFilter, ...searchFilter,
textContent: !searchFilter.textContent, textContent: !searchFilter.textContent,
}) });
} }}
/> />
<span className="label-text whitespace-nowrap"> <span className="label-text whitespace-nowrap">
{t("full_content")} {t("full_content")}
+1 -1
View File
@@ -8,7 +8,7 @@ const InstallApp = (props: Props) => {
const [isOpen, setIsOpen] = useState(true); const [isOpen, setIsOpen] = useState(true);
return isOpen && !isPWA() ? ( return isOpen && !isPWA() ? (
<div className="fixed left-0 right-0 bottom-10 w-full p-5"> <div className="fixed left-0 right-0 bottom-10 w-full">
<div className="mx-auto w-fit p-2 flex justify-between gap-2 items-center border border-neutral-content rounded-xl bg-base-300 backdrop-blur-md bg-opacity-80 max-w-md"> <div className="mx-auto w-fit p-2 flex justify-between gap-2 items-center border border-neutral-content rounded-xl bg-base-300 backdrop-blur-md bg-opacity-80 max-w-md">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
+27 -6
View File
@@ -3,6 +3,8 @@ import { Sort } from "@/types/global";
import { dropdownTriggerer } from "@/lib/client/utils"; import { dropdownTriggerer } from "@/lib/client/utils";
import { TFunction } from "i18next"; import { TFunction } from "i18next";
import useLocalSettingsStore from "@/store/localSettings"; import useLocalSettingsStore from "@/store/localSettings";
import { resetInfiniteQueryPagination } from "@/hooks/store/links";
import { useQueryClient } from "@tanstack/react-query";
type Props = { type Props = {
sortBy: Sort; sortBy: Sort;
@@ -12,6 +14,7 @@ type Props = {
export default function SortDropdown({ sortBy, setSort, t }: Props) { export default function SortDropdown({ sortBy, setSort, t }: Props) {
const { updateSettings } = useLocalSettingsStore(); const { updateSettings } = useLocalSettingsStore();
const queryClient = useQueryClient();
useEffect(() => { useEffect(() => {
updateSettings({ sortBy }); updateSettings({ sortBy });
@@ -39,7 +42,10 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) {
name="sort-radio" name="sort-radio"
className="radio checked:bg-primary" className="radio checked:bg-primary"
checked={sortBy === Sort.DateNewestFirst} checked={sortBy === Sort.DateNewestFirst}
onChange={() => setSort(Sort.DateNewestFirst)} onChange={() => {
resetInfiniteQueryPagination(queryClient, ["links"]);
setSort(Sort.DateNewestFirst);
}}
/> />
<span className="label-text whitespace-nowrap"> <span className="label-text whitespace-nowrap">
{t("date_newest_first")} {t("date_newest_first")}
@@ -57,7 +63,10 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) {
name="sort-radio" name="sort-radio"
className="radio checked:bg-primary" className="radio checked:bg-primary"
checked={sortBy === Sort.DateOldestFirst} checked={sortBy === Sort.DateOldestFirst}
onChange={() => setSort(Sort.DateOldestFirst)} onChange={() => {
resetInfiniteQueryPagination(queryClient, ["links"]);
setSort(Sort.DateOldestFirst);
}}
/> />
<span className="label-text whitespace-nowrap"> <span className="label-text whitespace-nowrap">
{t("date_oldest_first")} {t("date_oldest_first")}
@@ -75,7 +84,10 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) {
name="sort-radio" name="sort-radio"
className="radio checked:bg-primary" className="radio checked:bg-primary"
checked={sortBy === Sort.NameAZ} checked={sortBy === Sort.NameAZ}
onChange={() => setSort(Sort.NameAZ)} onChange={() => {
resetInfiniteQueryPagination(queryClient, ["links"]);
setSort(Sort.NameAZ);
}}
/> />
<span className="label-text whitespace-nowrap">{t("name_az")}</span> <span className="label-text whitespace-nowrap">{t("name_az")}</span>
</label> </label>
@@ -91,7 +103,10 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) {
name="sort-radio" name="sort-radio"
className="radio checked:bg-primary" className="radio checked:bg-primary"
checked={sortBy === Sort.NameZA} checked={sortBy === Sort.NameZA}
onChange={() => setSort(Sort.NameZA)} onChange={() => {
resetInfiniteQueryPagination(queryClient, ["links"]);
setSort(Sort.NameZA);
}}
/> />
<span className="label-text whitespace-nowrap">{t("name_za")}</span> <span className="label-text whitespace-nowrap">{t("name_za")}</span>
</label> </label>
@@ -107,7 +122,10 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) {
name="sort-radio" name="sort-radio"
className="radio checked:bg-primary" className="radio checked:bg-primary"
checked={sortBy === Sort.DescriptionAZ} checked={sortBy === Sort.DescriptionAZ}
onChange={() => setSort(Sort.DescriptionAZ)} onChange={() => {
resetInfiniteQueryPagination(queryClient, ["links"]);
setSort(Sort.DescriptionAZ);
}}
/> />
<span className="label-text whitespace-nowrap"> <span className="label-text whitespace-nowrap">
{t("description_az")} {t("description_az")}
@@ -125,7 +143,10 @@ export default function SortDropdown({ sortBy, setSort, t }: Props) {
name="sort-radio" name="sort-radio"
className="radio checked:bg-primary" className="radio checked:bg-primary"
checked={sortBy === Sort.DescriptionZA} checked={sortBy === Sort.DescriptionZA}
onChange={() => setSort(Sort.DescriptionZA)} onChange={() => {
resetInfiniteQueryPagination(queryClient, ["links"]);
setSort(Sort.DescriptionZA);
}}
/> />
<span className="label-text whitespace-nowrap"> <span className="label-text whitespace-nowrap">
{t("description_za")} {t("description_za")}
+33 -13
View File
@@ -159,20 +159,23 @@ const useUpdateLink = () => {
return data.response; return data.response;
}, },
onSuccess: (data) => { onSuccess: (data) => {
queryClient.setQueryData(["dashboardData"], (oldData: any) => { // queryClient.setQueryData(["dashboardData"], (oldData: any) => {
if (!oldData) return undefined; // if (!oldData) return undefined;
return oldData.map((e: any) => (e.id === data.id ? data : e)); // return oldData.map((e: any) => (e.id === data.id ? data : e));
}); // });
queryClient.setQueriesData({ queryKey: ["links"] }, (oldData: any) => { // queryClient.setQueriesData({ queryKey: ["links"] }, (oldData: any) => {
if (!oldData) return undefined; // if (!oldData) return undefined;
return { // return {
pages: oldData.pages.map((page: any) => // pages: oldData.pages.map((page: any) =>
page.map((item: any) => (item.id === data.id ? data : item)) // page.map((item: any) => (item.id === data.id ? data : item))
), // ),
pageParams: oldData.pageParams, // pageParams: oldData.pageParams,
}; // };
}); // });
queryClient.invalidateQueries({ queryKey: ["links"] }); // Temporary workaround
queryClient.invalidateQueries({ queryKey: ["dashboardData"] }); // Temporary workaround
queryClient.invalidateQueries({ queryKey: ["collections"] }); queryClient.invalidateQueries({ queryKey: ["collections"] });
queryClient.invalidateQueries({ queryKey: ["tags"] }); queryClient.invalidateQueries({ queryKey: ["tags"] });
@@ -425,6 +428,22 @@ const useBulkEditLinks = () => {
}); });
}; };
const resetInfiniteQueryPagination = async (
queryClient: any,
queryKey: any
) => {
queryClient.setQueriesData({ queryKey }, (oldData: any) => {
if (!oldData) return undefined;
return {
pages: oldData.pages.slice(0, 1),
pageParams: oldData.pageParams.slice(0, 1),
};
});
await queryClient.invalidateQueries(queryKey);
};
export { export {
useLinks, useLinks,
useAddLink, useAddLink,
@@ -434,4 +453,5 @@ export {
useUploadFile, useUploadFile,
useGetLink, useGetLink,
useBulkEditLinks, useBulkEditLinks,
resetInfiniteQueryPagination,
}; };
@@ -57,8 +57,8 @@ export default async function deleteCollection(
}, },
}); });
await removeFolder({ filePath: `archives/${collectionId}` }); removeFolder({ filePath: `archives/${collectionId}` });
await removeFolder({ filePath: `archives/preview/${collectionId}` }); removeFolder({ filePath: `archives/preview/${collectionId}` });
await removeFromOrders(userId, collectionId); await removeFromOrders(userId, collectionId);
@@ -100,8 +100,8 @@ async function deleteSubCollections(collectionId: number) {
where: { id: subCollection.id }, where: { id: subCollection.id },
}); });
await removeFolder({ filePath: `archives/${subCollection.id}` }); removeFolder({ filePath: `archives/${subCollection.id}` });
await removeFolder({ filePath: `archives/preview/${subCollection.id}` }); removeFolder({ filePath: `archives/preview/${subCollection.id}` });
} }
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "linkwarden", "name": "linkwarden",
"version": "v2.7.0", "version": "v2.7.1",
"main": "index.js", "main": "index.js",
"repository": "https://github.com/linkwarden/linkwarden.git", "repository": "https://github.com/linkwarden/linkwarden.git",
"author": "Daniel31X13 <daniel31x13@gmail.com>", "author": "Daniel31X13 <daniel31x13@gmail.com>",
+68 -2
View File
@@ -1186,10 +1186,42 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) {
providerAccountId: account?.providerAccountId, providerAccountId: account?.providerAccountId,
}, },
}); });
if (!existingUser && newSsoUsersDisabled) { if (!existingUser && newSsoUsersDisabled) {
return false; return false;
} }
// If user is already registered, link the provider
if (user.email && account) {
const findUser = await prisma.user.findFirst({
where: {
email: user.email,
},
include: {
accounts: true,
},
});
if (findUser && findUser.accounts.length === 0) {
await prisma.account.create({
data: {
userId: findUser.id,
type: account.type,
provider: account.provider,
providerAccountId: account.providerAccountId,
id_token: account.id_token,
access_token: account.access_token,
refresh_token: account.refresh_token,
expires_at: account.expires_at,
token_type: account.token_type,
scope: account.scope,
session_state: account.session_state,
},
});
}
}
} }
return true; return true;
}, },
async jwt({ token, trigger, user }) { async jwt({ token, trigger, user }) {
@@ -1198,13 +1230,28 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) {
token.id = user?.id as number; token.id = user?.id as number;
if (trigger === "signUp") { if (trigger === "signUp") {
const checkIfUserExists = await prisma.user.findUnique({ const userExists = await prisma.user.findUnique({
where: { where: {
id: token.id, id: token.id,
}, },
include: {
accounts: true,
},
}); });
if (checkIfUserExists && !checkIfUserExists.username) { // Verify SSO user email
if (userExists && userExists.accounts.length > 0) {
await prisma.user.update({
where: {
id: userExists.id,
},
data: {
emailVerified: new Date(),
},
});
}
if (userExists && !userExists.username) {
const autoGeneratedUsername = const autoGeneratedUsername =
"user" + Math.round(Math.random() * 1000000000); "user" + Math.round(Math.random() * 1000000000);
@@ -1217,6 +1264,22 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) {
}, },
}); });
} }
} else if (trigger === "signIn") {
const user = await prisma.user.findUnique({
where: {
id: token.id,
},
});
if (user && !user.username) {
const autoGeneratedUsername =
"user" + Math.round(Math.random() * 1000000000);
await prisma.user.update({
where: { id: user.id },
data: { username: autoGeneratedUsername },
});
}
} }
return token; return token;
@@ -1224,6 +1287,8 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) {
async session({ session, token }) { async session({ session, token }) {
session.user.id = token.id; session.user.id = token.id;
console.log("session", session);
if (STRIPE_SECRET_KEY) { if (STRIPE_SECRET_KEY) {
const user = await prisma.user.findUnique({ const user = await prisma.user.findUnique({
where: { where: {
@@ -1235,6 +1300,7 @@ export default async function auth(req: NextApiRequest, res: NextApiResponse) {
}); });
if (user) { if (user) {
//
const subscribedUser = await verifySubscription(user); const subscribedUser = await verifySubscription(user);
} }
} }