diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index b24d163..b1e0b29 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -1,4 +1,4 @@
-name: Code-Coverage
+name: Code Coverage
on: [push, pull_request]
@@ -26,18 +26,18 @@ jobs:
# Maps tcp port 5432 on service container to the host
- 5432:5432
- strategy:
- matrix:
- node-version: [14.x]
+ # strategy:
+ # matrix:
+ # node-version: [14.x]
steps:
- name: Checkout repository
uses: actions/checkout@v2
- - name: Set up Node.js ${{ matrix.node-version }}
+ - name: Set up Node.js 14.x
uses: actions/setup-node@v1
with:
- node-version: ${{ matrix.node-version }}
+ node-version: 14.x
- name: Cache node modules
uses: actions/cache@v2
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..c936d0a
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,29 @@
+name: Publish
+# Only trigger, when the build workflow succeeded
+
+on:
+ workflow_run:
+ workflows: ["Code Coverage"]
+ branches: [master]
+ types:
+ - completed
+ # - requested
+jobs:
+ Publish:
+ if: ${{ github.event.workflow_run.conclusion == 'success' }}
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ - name: Build docker container
+ run: docker build . -t ghcr.io/$(echo $GITHUB_ACTOR | tr '[:upper:]' '[:lower:]')/linkin:latest -t ghcr.io/$(echo $GITHUB_ACTOR | tr '[:upper:]' '[:lower:]')/linkin:$(echo $GITHUB_SHA | head -c7)
+
+ - name: Login to docker
+ run: echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{github.actor}} --password-stdin
+
+ - name: Push
+ run: docker push ghcr.io/$(echo $GITHUB_ACTOR | tr '[:upper:]' '[:lower:]')/linkin:latest
+# docker build . -t ghcr.io/aa/linkin:latest -t ghcr.io/aa/linkin:${echo $GITHUB_SHA | head -c7}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5bbb0c5..e51ffd2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -73,13 +73,12 @@ Linkin has 2 main branches
1. [master](https://github.com/RizkyRajitha/linkin/tree/master) branch - will have the code from the latest release. only updates on a release.
2. [dev](https://github.com/RizkyRajitha/linkin/tree/dev) branch - all the development carries out in this branch. the latest code will be available in this branch, **all the pull requests should be made to dev branch** since prs could be tested and modified for the final release phase.
-other than the above branches there can feature specific branches for the continence.
+ other than the above branches there can feature specific branches for the continence.
## Making pull request
when making a pull request please create your feature branch using the **[dev](https://github.com/RizkyRajitha/linkin/tree/dev)** branch (`checkout using dev branch`), and develop in it locally. avoid installing additional dependencies unless clarified through a maintainer. make the pr to the **[dev](https://github.com/RizkyRajitha/linkin/tree/dev)** branch.
-
## File Structure
```
@@ -239,7 +238,8 @@ there are 6 forms inside the `Formwrapper`.form has been divided into this manne
3. Colors (form that holds color details) [colorform.js](./components/colorform.js)
4. Fonts (form that holds font details) [fontform.js](./components/fontform.js)
5. Link Data (a form that configures links and their respective details, this form have it's how API posing to facilitate live refresh in links) [linksform.js](./components/linksform.js)
-6. Updated Password (a form that updates the password) [passwordchangeform.js](./components/passwordchangeform.js)
+6. Social Data (a form that configures **social** links and their respective details, this form have it's how API posing to facilitate live refresh in links) [socialform.js](./components/socialform.js)
+7. Updated Password (a form that updates the password) [passwordchangeform.js](./components/passwordchangeform.js)
#### Preview
@@ -257,16 +257,24 @@ Linkin levarages next js built-in [api-routes](https://nextjs.org/docs/api-route
this removes the requirement of additional API deployment, making Linkin cleaner and easier to deploy.
all the API routes live in [/pages/api/](./pages/api/) directory.
-Linkin has 8 API routes
+Linkin has 12 API routes:
1. login [login.js](./pages/api/login.js)
2. logout [logout.js](./pages/api/logout.js)
3. changepassword [changepassword.js](./pages/api/changepassword.js)
4. updatepagedata [updatepagedata.js](./pages/api/updatepagedata.js)
-5. insertpagelinks [insertpagelinks.js](./pages/api/insertpagelinks.js)
-6. updatepagelinks [updatepagelinks.js](./pages/api/updatepagelinks.js)
-7. deletepagelink [deletepagelink.js](./pages/api/deletepagelink.js)
-8. reorderlinks [reorderlinks.js](./pages/api/reorderlinks.js)
+5. insertlinks
+ - For pages form: [insertlinks.js](./pages/api/pages/insertlinks.js)
+ - For social form: [insertlinks.js](./pages/api/social/insertlinks.js)
+6. updatelinks
+ - For pages form: [updatelinks.js](./pages/api/pages/updatelinks.js)
+ - For social form: [updatelinks.js](./pages/api/social/updatelinks.js)
+7. deletelink
+ - For pages form: [deletelink.js](./pages/api/pages/deletelink.js)
+ - For social form: [deletelink.js](./pages/api/social/deletelink.js)
+8. reorderlinks
+ - For pages form [reorderlinks.js](./pages/api/pages/reorderlinks.js)
+ - For social form [reorderlinks.js](./pages/api/social/reorderlinks.js)
| API | method | data | response |
| --------------- | ------ | ----------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
diff --git a/README.md b/README.md
index c39b718..741160e 100644
--- a/README.md
+++ b/README.md
@@ -6,15 +6,15 @@
## Linkin is a customizable self-hosted link tree application.
-### 100% Free and Open Source π―
+### Free and Open Source π―
### Self Hosted, you own your data π½
-### Customize your link tree with few clicks with a feature-rich dashboard π€
+### Customize your link tree with few clicks using a feature-rich dashboard π€
### SEO friendly design built using Next js πΈοΈ
-### Supports 3 one-click deploy hosting providers π
+### Supports one-click deploy using multiple cloud providers π
diff --git a/__tests__/prismalinkdata.test.js b/__tests__/prismalinkdata.test.js
index 3f56d8e..842f7e6 100644
--- a/__tests__/prismalinkdata.test.js
+++ b/__tests__/prismalinkdata.test.js
@@ -1,5 +1,5 @@
const path = require("path");
-const Prisma = require("../db/dbconprisma");
+const { default: Prisma } = require("../db/dbconprisma");
require("dotenv").config({
path: path.join(__dirname, "../", ".env"),
@@ -10,21 +10,37 @@ const {
insertPageLinks,
updateLink,
deleteLink,
+ reorderLinks,
} = require("../lib/dbfuncprisma");
describe("Test Link data", () => {
+ beforeAll(async () => {
+ await Prisma.linkdata.deleteMany();
+ await Prisma.linkdata.create({
+ data: {
+ id: 1,
+ pagedataid: 1,
+ iconClass: "fas fa-link",
+ displayText: "Welcome to LinkIn",
+ linkUrl: "https://github.com/RizkyRajitha/linkin",
+ bgColor: "#2C6BED",
+ active: true,
+ },
+ });
+ });
+
beforeEach(() => {
jest.spyOn(console, "log").mockImplementation(() => {});
});
+
afterAll(async () => {
- await Prisma.default.$disconnect();
+ await Prisma.$disconnect();
});
test("get link data", async () => {
let { linkData } = await getLinkData();
- //console.info(linkData);
+
const expectedLink = {
- id: 1,
pagedataid: 1,
iconClass: "fas fa-link",
displayText: "Welcome to LinkIn",
@@ -40,9 +56,8 @@ describe("Test Link data", () => {
test("get link data without active", async () => {
let { linkData } = await getLinkData(false);
- //console.log(linkData);
+
const expectedLink = {
- id: 1,
pagedataid: 1,
iconClass: "fas fa-link",
displayText: "Welcome to LinkIn",
@@ -59,7 +74,6 @@ describe("Test Link data", () => {
test("get link data without active", async () => {
let { linkData } = await getLinkData(false);
const expectedLink = {
- id: 1,
pagedataid: 1,
iconClass: "fas fa-link",
displayText: "Welcome to LinkIn",
@@ -87,8 +101,6 @@ describe("Test Link data", () => {
await insertPageLinks(linkTobeinserted);
let updatedLinkData = await getLinkData();
- //console.info(updatedLinkData);
-
expect(updatedLinkData.linkData).toMatchObject([
...beforeUpdateLinkData.linkData,
linkTobeinserted,
@@ -96,10 +108,10 @@ describe("Test Link data", () => {
});
test("delete link data ", async () => {
- let beforeUpdateLinkData = await getLinkData();
+ let beforeUpdateLinkData = await (await getLinkData()).linkData;
//console.info(beforeUpdateLinkData.linkData[1].id);
- let delelement = beforeUpdateLinkData.linkData.filter((ele) => {
+ let delelement = beforeUpdateLinkData.filter((ele) => {
return ele.id !== 1;
});
@@ -108,9 +120,11 @@ describe("Test Link data", () => {
//console.info(updatedLinkData);
- expect(updatedLinkData.linkData).toMatchObject([
- beforeUpdateLinkData.linkData[0],
- ]);
+ expectedLink = beforeUpdateLinkData.filter((ele) => {
+ return ele.id === 1;
+ });
+
+ expect(updatedLinkData.linkData[0]).toMatchObject(expectedLink[0]);
});
test("update link data ", async () => {
@@ -136,25 +150,42 @@ describe("Test Link data", () => {
// });
});
- test("revert updated link data ", async () => {
- const linkTobeUpdated = {
- id: 1,
+ test("reorder links", async () => {
+ await insertPageLinks({
pagedataid: 1,
iconClass: "fas fa-link",
- displayText: "Welcome to LinkIn",
+ displayText: "link2",
linkUrl: "https://github.com/RizkyRajitha/linkin",
bgColor: "#2C6BED",
active: true,
- };
+ });
- await updateLink(linkTobeUpdated);
- let updatedLinkData = await getLinkData();
+ let orderList = await (
+ await getLinkData()
+ ).linkData.map((item) => {
+ return { id: item.id, orderIndex: item.orderIndex };
+ });
- //console.info(updatedLinkData);
+ // console.info(orderList);
- expect(updatedLinkData.linkData[0]).toMatchObject(linkTobeUpdated);
- // linkData.forEach((element) => {
- // expect(element).toMatchObject(expectedLink);
- // });
+ let reOrderList = orderList.map((item, index) => {
+ return { id: item.id, orderIndex: index };
+ });
+
+ await reorderLinks(reOrderList);
+
+ let updatedOrderList = await (
+ await getLinkData()
+ ).linkData.map((item) => {
+ return { orderIndex: item.orderIndex };
+ });
+
+ // expect(updatedLinkData.linkData[0]).toMatchObject(linkTobeUpdated);
+
+ let expectedOrderList = [{ orderIndex: 0 }, { orderIndex: 1 }];
+
+ expectedOrderList.forEach((element, index) => {
+ expect(element).toMatchObject(updatedOrderList[index]);
+ });
});
});
diff --git a/__tests__/prismapagedata.test.js b/__tests__/prismapagedata.test.js
index d532e1e..fceb231 100644
--- a/__tests__/prismapagedata.test.js
+++ b/__tests__/prismapagedata.test.js
@@ -1,5 +1,5 @@
const path = require("path");
-const Prisma = require("../db/dbconprisma");
+const { default: Prisma } = require("../db/dbconprisma");
require("dotenv").config({
path: path.join(__dirname, "../", ".env"),
@@ -12,14 +12,54 @@ const {
} = require("../lib/dbfuncprisma");
describe("page data functions", () => {
+ beforeAll(async () => {
+ await Prisma.linkdata.deleteMany();
+ await Prisma.pagedata.deleteMany();
+ await Prisma.pagedata.create({
+ data: {
+ id: 1,
+ avatarUrl:
+ "https://res.cloudinary.com/dijjqfsto/image/upload/v1621666671/linkin_logo_1_jcuvr3.png",
+ avatarwidth: "50",
+ bgColor: "#7ea2ff",
+ accentColor: "#bdd7ff",
+ handlerText: "LinkIn",
+ footerText: "Powered by Linkin",
+ handlerFontSize: "20",
+ handlerFontColor: "#ffffff",
+ },
+ });
+
+ await Prisma.linkdata.createMany({
+ data: [
+ {
+ pagedataid: 1,
+ iconClass: "fas fa-link",
+ displayText: "Welcome to LinkIn",
+ linkUrl: "https://github.com/RizkyRajitha/linkin",
+ bgColor: "#2C6BED",
+ active: true,
+ },
+ {
+ pagedataid: 1,
+ iconClass: "fas fa-link",
+ displayText: "Inactive",
+ linkUrl: "https://github.com/RizkyRajitha/linkin",
+ bgColor: "#2C6BED",
+ active: false,
+ },
+ ],
+ });
+ });
+
beforeEach(() => {
jest.spyOn(console, "log").mockImplementation(() => {});
});
afterAll(async () => {
- await Prisma.default.$disconnect();
+ await Prisma.$disconnect();
});
- test("get page data ", async () => {
+ test("get pagedata ", async () => {
let { pageData } = await getPageData();
const expectedUser = {
id: 1,
@@ -38,9 +78,8 @@ describe("page data functions", () => {
expect(pageData).toMatchObject(expectedUser);
});
- test("get page data with links data with active links", async () => {
+ test("get page data with all links data", async () => {
let pageDatawLinks = await getPageDatawLinkData();
- console.log(pageDatawLinks);
const expectedPageDataWLinks = {
pageData: {
id: 1,
@@ -62,22 +101,34 @@ describe("page data functions", () => {
},
linkData: [
{
- id: 1,
pagedataid: 1,
iconClass: "fas fa-link",
displayText: "Welcome to LinkIn",
linkUrl: "https://github.com/RizkyRajitha/linkin",
bgColor: "#2C6BED",
active: true,
+ accentColor: null,
+ borderRadius: null,
+ textColor: null,
+ },
+ {
+ pagedataid: 1,
+ iconClass: "fas fa-link",
+ displayText: "Inactive",
+ linkUrl: "https://github.com/RizkyRajitha/linkin",
+ bgColor: "#2C6BED",
+ active: false,
+ accentColor: null,
+ borderRadius: null,
+ textColor: null,
},
],
};
expect(pageDatawLinks).toMatchObject(expectedPageDataWLinks);
});
- test("get page data with links data without active links", async () => {
+ test("get page data with only active links", async () => {
let pageDatawLinks = await getPageDatawLinkData(false);
- //console.info(pageDatawLinks);
const expectedPageDataWLinks = {
pageData: {
id: 1,
@@ -99,13 +150,15 @@ describe("page data functions", () => {
},
linkData: [
{
- id: 1,
pagedataid: 1,
iconClass: "fas fa-link",
displayText: "Welcome to LinkIn",
linkUrl: "https://github.com/RizkyRajitha/linkin",
bgColor: "#2C6BED",
active: true,
+ accentColor: null,
+ borderRadius: null,
+ textColor: null,
},
],
};
@@ -131,38 +184,8 @@ describe("page data functions", () => {
fontFamily: "Roboto",
};
- //let updatedPageData = await updatePageData(beforUpdatePageData);
- await updatePageData(beforUpdatePageData);
- let updatedPageData = await getPageData();
- //console.info(updatedPageData);
-
- expect(updatedPageData.pageData).toMatchObject(beforUpdatePageData);
- });
-
- test("revert Updated page data", async () => {
- let beforUpdatePageData = {
- id: 1,
- avatarUrl:
- "https://res.cloudinary.com/dijjqfsto/image/upload/v1621666671/linkin_logo_1_jcuvr3.png",
- avatarheight: null,
- avatarwidth: "50",
- bgColor: "#7ea2ff",
- accentColor: "#bdd7ff",
- handlerText: "LinkIn",
- handlerLink: null,
- footerText: 'Powered by Linkin',
- bgImgUrl: null,
- handlerFontSize: "20",
- handlerFontColor: "#ffffff",
- active: true,
- fontFamily: null,
- fontUrl: null,
- };
-
- //let updatedPageData = await updatePageData(beforUpdatePageData);
await updatePageData(beforUpdatePageData);
let updatedPageData = await getPageData();
- //console.info(updatedPageData);
expect(updatedPageData.pageData).toMatchObject(beforUpdatePageData);
});
diff --git a/__tests__/prismausers.test.js b/__tests__/prismausers.test.js
index 379789b..e23854e 100644
--- a/__tests__/prismausers.test.js
+++ b/__tests__/prismausers.test.js
@@ -1,23 +1,33 @@
const path = require("path");
+
require("dotenv").config({
path: path.join(__dirname, "../", ".env"),
});
-const Prisma = require("../db/dbconprisma");
+const { default: Prisma } = require("../db/dbconprisma");
const { getUser, changePassword } = require("../lib/dbfuncprisma");
describe("Test User functions", () => {
+ beforeAll(async () => {
+ await Prisma.users.deleteMany();
+ await Prisma.users.create({
+ data: {
+ username: "admin",
+ password:
+ "$2b$10$gKoU.xdV9vrGY2wEW0KAnuBmQeYxOUgXRHS9f8Sgx40m7kxpejddG",
+ },
+ });
+ });
+
beforeEach(() => {
jest.spyOn(console, "log").mockImplementation(() => {});
});
afterAll(async () => {
- await Prisma.default.$disconnect();
+ await Prisma.$disconnect();
});
test("get admin user", async () => {
- //console.warn("test 1 --------------------");
-
let user = await getUser("admin");
const expectedUser = {
@@ -28,8 +38,6 @@ describe("Test User functions", () => {
});
test("change Password of admin", async () => {
- //console.warn("test 2 --------------------");
-
try {
let newUserPasswordData = {
username: "admin",
@@ -49,29 +57,4 @@ describe("Test User functions", () => {
console.error(error);
}
});
-
- test("revert Password of admin", async () => {
- //console.warn("test 2 --------------------");
-
- try {
- let newUserPasswordData = {
- username: "admin",
- newhashedpassword:
- "$2b$10$gKoU.xdV9vrGY2wEW0KAnuBmQeYxOUgXRHS9f8Sgx40m7kxpejddG",
- };
-
- const expectedUser = {
- username: "admin",
- password:
- "$2b$10$gKoU.xdV9vrGY2wEW0KAnuBmQeYxOUgXRHS9f8Sgx40m7kxpejddG",
- };
-
- await changePassword(newUserPasswordData);
- let user = await getUser("admin");
- //console.info(user);
- expect(user).toMatchObject(expectedUser);
- } catch (error) {
- console.error(error);
- }
- });
});
diff --git a/components/colorform.js b/components/colorform.js
index d44b1b7..8afefdd 100644
--- a/components/colorform.js
+++ b/components/colorform.js
@@ -1,15 +1,57 @@
+import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import styles from "../styles/form.module.css";
const ColorForm = ({ data, update, loading }) => {
- const { register, handleSubmit } = useForm({
+ const [isGradeints, setisGradeints] = useState(false);
+ const [colorWiseGradeints, setcolorWiseGradeints] = useState({});
+
+ useEffect(() => {
+ //create an object to store color state of each input (gradient or normal(hex))
+ let colorWiseGradientState = {
+ bgColor: data.bgColor,
+ accentColor: data.accentColor,
+ handlerFontColor: data.handlerFontColor,
+ footerBgColor: data.footerBgColor,
+ footerTextColor: data.footerTextColor,
+ avatarBorderColor: data.avatarBorderColor,
+ handlerDescriptionFontColor: data.handlerDescriptionFontColor,
+ };
+
+ // check whether the current value is gradient or hex
+ for (const [key, value] of Object.entries(colorWiseGradientState)) {
+ if (!String(value).startsWith("#")) {
+ colorWiseGradientState[key] = true;
+ setisGradeints(true);
+ } else {
+ colorWiseGradientState[key] = false;
+ }
+ }
+ setcolorWiseGradeints(colorWiseGradientState);
+ }, [data]);
+
+ // reset the form when the gradeint switch state change
+ useEffect(() => {
+ reset({
+ bgColor: data.bgColor,
+ accentColor: data.accentColor,
+ handlerFontColor: data.handlerFontColor,
+ footerBgColor: data.footerBgColor,
+ footerTextColor: data.footerTextColor,
+ avatarBorderColor: data.avatarBorderColor,
+ handlerDescriptionFontColor: data.handlerDescriptionFontColor,
+ });
+ }, [isGradeints]);
+
+ const { register, handleSubmit, reset } = useForm({
defaultValues: {
bgColor: data.bgColor,
accentColor: data.accentColor,
handlerFontColor: data.handlerFontColor,
footerBgColor: data.footerBgColor,
footerTextColor: data.footerTextColor,
+ avatarBorderColor: data.avatarBorderColor,
handlerDescriptionFontColor: data.handlerDescriptionFontColor,
},
});
@@ -20,16 +62,37 @@ const ColorForm = ({ data, update, loading }) => {