postgres
This commit is contained in:
parent
b0f3e6c576
commit
e44d419b58
15 changed files with 233 additions and 82 deletions
29
README.md
29
README.md
|
@ -1,20 +1,37 @@
|
|||
# next-url-shortener
|
||||
A simple URL shortener using [Next.js](https://nextjs.org/) 14 server actions, [Postgres](https://www.postgresql.org/) or [SurrealDB](https://surrealdb.com/), [Shadcn/ui](http://ui.shadcn.com/) and [Tailwind](https://tailwindcss.com/).
|
||||
|
||||
# Install
|
||||
Copy [docker-compose.yml](./docker-compose.yml)
|
||||
### Copy one
|
||||
- [docker-compose-postgres.yml](./docker-compose-postgres.yml)
|
||||
- [docker-compose-surrealdb.yml](./docker-compose-surrealdb.yml)
|
||||
|
||||
Create data folder next to docker-compose.yml and .env, and set the data type in .env
|
||||
```
|
||||
├── data
|
||||
│ └── surrealdb
|
||||
├── docker-compose.yml
|
||||
├── docker-compose-postgres.yml
|
||||
└── .env
|
||||
```
|
||||
|
||||
`docker compose up --pull always -d`
|
||||
### Then run:
|
||||
- `docker compose -f docker-compose-postgres.yml up --pull always -d`
|
||||
- `docker compose -f docker-compose-surrealdb.yml up --pull always -d`
|
||||
|
||||
## Example .env
|
||||
```
|
||||
# If not using docker, use 0.0.0.0:8000
|
||||
DB_URL_PORT=surrealdb:8000
|
||||
# postgres or surrealdb
|
||||
DB_TYPE=postgres
|
||||
|
||||
# For surrealdb: If using docker surrealdb:8000, if not use 0.0.0.0:8000
|
||||
# For postgres: If using docker postgres:5432, if not use 0.0.0.0:5432
|
||||
DB_URL_PORT=postgres:5432
|
||||
|
||||
# postgres
|
||||
POSTGRES_USER=root
|
||||
POSTGRES_PASSWORD=root
|
||||
POSTGRES_DB=url
|
||||
|
||||
# surrealdb
|
||||
DB_USER=root
|
||||
DB_PASSWORD=root
|
||||
```
|
||||
|
|
36
docker-compose-postgres.yml
Normal file
36
docker-compose-postgres.yml
Normal file
|
@ -0,0 +1,36 @@
|
|||
version: "3"
|
||||
services:
|
||||
next-url-shortener:
|
||||
container_name: next-url-shortener
|
||||
build: .
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- 3000:3000
|
||||
depends_on:
|
||||
- postgres
|
||||
networks:
|
||||
- postgres
|
||||
|
||||
postgres:
|
||||
container_name: postgres
|
||||
image: postgres:latest
|
||||
env_file:
|
||||
- .env
|
||||
restart: always
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
cpus: 1
|
||||
ports:
|
||||
- 5432:5432
|
||||
volumes:
|
||||
- ./data/postgres:/var/lib/postgresql/data
|
||||
networks:
|
||||
- postgres
|
||||
|
||||
volumes:
|
||||
data:
|
||||
|
||||
networks:
|
||||
postgres:
|
17
package-lock.json
generated
17
package-lock.json
generated
|
@ -22,8 +22,9 @@
|
|||
"dotenv": "^16.4.4",
|
||||
"next": "14.1.0",
|
||||
"next-themes": "^0.2.1",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"postgres": "^3.4.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.50.1",
|
||||
"surrealdb.js": "^0.11.0",
|
||||
"swr": "^2.2.5",
|
||||
|
@ -4465,6 +4466,18 @@
|
|||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||
},
|
||||
"node_modules/postgres": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.3.tgz",
|
||||
"integrity": "sha512-iHJn4+M9vbTdHSdDzNkC0crHq+1CUdFhx+YqCE+SqWxPjm+Zu63jq7yZborOBF64c8pc58O5uMudyL1FQcHacA==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/porsager"
|
||||
}
|
||||
},
|
||||
"node_modules/prelude-ls": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
|
||||
|
|
|
@ -23,8 +23,8 @@
|
|||
"dotenv": "^16.4.4",
|
||||
"next": "14.1.0",
|
||||
"next-themes": "^0.2.1",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"postgres": "^3.4.3",
|
||||
"react": "^18.2.0",
|
||||
"react-hook-form": "^7.50.1",
|
||||
"surrealdb.js": "^0.11.0",
|
||||
"swr": "^2.2.5",
|
||||
|
|
|
@ -1,16 +1,35 @@
|
|||
"use server";
|
||||
import { initConnection } from "@/components/db-utils";
|
||||
import { initConnectionPostgres, initConnectionSurreal } from "@/components/db-utils";
|
||||
|
||||
export async function querydb(slug: string) {
|
||||
let long_url = undefined;
|
||||
try {
|
||||
let db = await initConnection();
|
||||
if (process.env.DB_TYPE === "surrealdb") {
|
||||
|
||||
let db = await initConnectionSurreal();
|
||||
let long_url = await db.query(`
|
||||
update url:[$id] set clicks = clicks + 1 ;
|
||||
update url:[$id]
|
||||
set clicks = clicks + 1;
|
||||
select * from url:[$id];
|
||||
`, { id: slug });
|
||||
|
||||
// @ts-ignore
|
||||
long_url = long_url[0][0].long_url;
|
||||
}
|
||||
|
||||
if (process.env.DB_TYPE === "postgres") {
|
||||
let sql = await initConnectionPostgres();
|
||||
long_url = await sql`
|
||||
update url set
|
||||
clicks = clicks + 1,
|
||||
date_accessed = now()
|
||||
where id = ${slug}
|
||||
returning long_url;
|
||||
`;
|
||||
long_url = long_url[0].long_url;
|
||||
}
|
||||
|
||||
console.log(long_url);
|
||||
return long_url;
|
||||
} catch (e) {
|
||||
return;
|
||||
|
|
|
@ -1,27 +1,70 @@
|
|||
"use server";
|
||||
import { initConnection } from "@/components/db-utils";
|
||||
import { initConnectionPostgres, initConnectionSurreal } from "@/components/db-utils";
|
||||
import { formSchema } from "./schema";
|
||||
|
||||
function generateRandomString(length: number) {
|
||||
const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let randomString = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * charset.length);
|
||||
randomString += charset[randomIndex];
|
||||
}
|
||||
return randomString;
|
||||
}
|
||||
|
||||
export async function querydb(prevState: any, formData: FormData) {
|
||||
try {
|
||||
const values = formSchema.safeParse(formData)
|
||||
if (!values.success) {
|
||||
return { error: values.error };
|
||||
}
|
||||
const long_url = values.data.url;
|
||||
let url = undefined;
|
||||
|
||||
let db = await initConnection();
|
||||
let url = await db.query(`
|
||||
try {
|
||||
if (process.env.DB_TYPE === "surrealdb") {
|
||||
let db = await initConnectionSurreal();
|
||||
url = await db.query(`
|
||||
create url:[rand::string(8)] CONTENT {
|
||||
long_url: string::replace(string::replace($long_url, "https://", ""), "http://", ""),
|
||||
long_url: $long_url,
|
||||
clicks: 0,
|
||||
date_added: time::now(),
|
||||
date_accessed: <future> { time::now() }
|
||||
} return id[0];
|
||||
`, { long_url: long_url });
|
||||
|
||||
`, {
|
||||
long_url: long_url.replace("https://", "").replace("http://", "")
|
||||
});
|
||||
// @ts-ignore
|
||||
url = url[0][0].id;
|
||||
}
|
||||
|
||||
console.log(long_url);
|
||||
if (process.env.DB_TYPE === "postgres") {
|
||||
let sql = await initConnectionPostgres();
|
||||
await sql`
|
||||
create table if not exists url (
|
||||
id text primary key,
|
||||
clicks integer not null,
|
||||
long_url text not null,
|
||||
date_added timestamp not null,
|
||||
date_accessed timestamp not null
|
||||
);
|
||||
`;
|
||||
|
||||
url = await sql`
|
||||
insert into url (id, long_url, clicks, date_added, date_accessed)
|
||||
values (
|
||||
${generateRandomString(8)},
|
||||
${long_url.replace("https://", "").replace("http://", "")},
|
||||
0,
|
||||
now(),
|
||||
now()
|
||||
)
|
||||
returning id;
|
||||
`;
|
||||
|
||||
url = url[0].id;
|
||||
}
|
||||
console.log(url);
|
||||
|
||||
return { url: url };
|
||||
} catch (e) {
|
||||
|
|
|
@ -6,12 +6,16 @@ import {
|
|||
CardTitle
|
||||
} from "@/components/ui/card";
|
||||
|
||||
export default function GlobalError() {
|
||||
export default function GlobalError({
|
||||
error,
|
||||
}: {
|
||||
error: Error & { digest?: string }
|
||||
}) {
|
||||
return (
|
||||
<CardGrid max_rows={1}>
|
||||
<CardGrid maxCols={1}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-center text-2xl text-red-400">Error</CardTitle>
|
||||
<CardTitle className="text-center text-2xl text-red-400">Error + {String(error)}</CardTitle>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</CardGrid>
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<CardGrid max_rows={1}>
|
||||
<CardGrid maxCols={1}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-center text-2xl text-red-400">404 - Not Found</CardTitle>
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
"use server";
|
||||
import { initConnection } from "@/components/db-utils";
|
||||
import { initConnectionPostgres, initConnectionSurreal } from "@/components/db-utils";
|
||||
|
||||
export async function querydb() {
|
||||
try {
|
||||
let db = await initConnection();
|
||||
let stats = [];
|
||||
if (process.env.DB_TYPE === "surrealdb") {
|
||||
let db = await initConnectionSurreal();
|
||||
// console.log(db);
|
||||
let stats = await db.query(`
|
||||
stats = await db.query(`
|
||||
select * from url
|
||||
order by clicks desc
|
||||
limit 50;
|
||||
|
@ -13,6 +15,16 @@ export async function querydb() {
|
|||
|
||||
// @ts-ignore
|
||||
stats = stats[0];
|
||||
}
|
||||
|
||||
if (process.env.DB_TYPE === "postgres") {
|
||||
let sql = await initConnectionPostgres();
|
||||
stats = await sql`
|
||||
select * from url
|
||||
order by clicks desc
|
||||
limit 50;
|
||||
`;
|
||||
}
|
||||
|
||||
return stats;
|
||||
} catch (e) {
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
|
||||
export default function Loading() {
|
||||
return (
|
||||
<CardGrid max_rows={1}>
|
||||
<CardGrid maxCols={1}>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-1xl text-amber-400">Loading...</CardTitle>
|
||||
|
|
|
@ -44,7 +44,7 @@ export default function StatsPage() {
|
|||
return data.length === 0 ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<CardGrid max_rows={1}>
|
||||
<CardGrid maxCols={1}>
|
||||
<Card>
|
||||
{/* @ts-ignore */}
|
||||
<DataTable columns={columns} data={data} />
|
||||
|
|
|
@ -1,37 +1,30 @@
|
|||
"use client";
|
||||
export default function CardGrid({
|
||||
max_rows = 4,
|
||||
maxCols: maxCols = 4,
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: {
|
||||
max_rows?: number;
|
||||
maxCols?: number;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
let baseClassName = `hidden items-start justify-center gap-6 rounded-lg p-8 md:grid`;
|
||||
let baseClassName = `hidden items-start justify-center gap-6 rounded-lg p-8 md:grid md:grid-cols-1`;
|
||||
|
||||
switch (max_rows) {
|
||||
case 1:
|
||||
baseClassName += " md:grid-cols-1 ";
|
||||
break;
|
||||
case 2:
|
||||
baseClassName += " md:grid-cols-1 lg:grid-cols-2 ";
|
||||
break;
|
||||
case 3:
|
||||
baseClassName += " md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 ";
|
||||
break;
|
||||
case 4:
|
||||
baseClassName += " md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 ";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
if (maxCols >= 2) {
|
||||
baseClassName += " lg:grid-cols-2";
|
||||
}
|
||||
if (maxCols >= 3) {
|
||||
baseClassName += " xl:grid-cols-3";
|
||||
}
|
||||
if (maxCols >= 4) {
|
||||
baseClassName += " 2xl:grid-cols-4";
|
||||
}
|
||||
|
||||
if (className == undefined) {
|
||||
className = baseClassName;
|
||||
} else {
|
||||
className = baseClassName + className;
|
||||
className = baseClassName + " " + className;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
"use server";
|
||||
import "dotenv";
|
||||
import postgres, { Sql } from 'postgres';
|
||||
import { Surreal } from "surrealdb.js";
|
||||
const db = new Surreal();
|
||||
|
||||
export async function initConnection(): Promise<Surreal> {
|
||||
export async function initConnectionSurreal(): Promise<Surreal> {
|
||||
try {
|
||||
db.connect("ws://" + process.env.DB_URL_PORT + "/rpc", {
|
||||
namespace: "url",
|
||||
|
@ -19,3 +19,17 @@ export async function initConnection(): Promise<Surreal> {
|
|||
|
||||
return db;
|
||||
}
|
||||
|
||||
export async function initConnectionPostgres(): Promise<Sql<{}>> {
|
||||
const DB_URL_PORT = (process.env.DB_URL_PORT || "postgres:5432").split(":");
|
||||
const sql = postgres({
|
||||
host: DB_URL_PORT[0],
|
||||
port: parseInt(DB_URL_PORT[1]),
|
||||
user: process.env.POSTGRES_USER || "root",
|
||||
password: process.env.POSTGRES_PASSWORD || "root",
|
||||
database: process.env.POSTGRES_DB || "url",
|
||||
max: 100,
|
||||
onnotice: () => { },
|
||||
});
|
||||
return sql;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue