feat: add api
This commit is contained in:
parent
ad7ab6db86
commit
10324d5799
11
db/db.ts
Normal file
11
db/db.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Kysely, ParseJSONResultsPlugin } from "kysely";
|
||||||
|
import { DB as Sqlite } from "sqlite";
|
||||||
|
import { DenoSqliteDialect } from "./deno-sqlite-dialect.ts";
|
||||||
|
import { Database } from "./type.ts";
|
||||||
|
|
||||||
|
export const db = new Kysely<Database>({
|
||||||
|
dialect: new DenoSqliteDialect({
|
||||||
|
database: new Sqlite("stock.db")
|
||||||
|
}),
|
||||||
|
plugins: [new ParseJSONResultsPlugin()]
|
||||||
|
});
|
27
db/deno-sqlite-dialect-config.ts
Normal file
27
db/deno-sqlite-dialect-config.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/// The MIT License (MIT)
|
||||||
|
/// Copyright (c) 2023 Alex Gleason
|
||||||
|
/// Copyright (c) 2022 Sami Koskimäki
|
||||||
|
/// https://gitlab.com/soapbox-pub/kysely-deno-sqlite
|
||||||
|
|
||||||
|
import type { SqliteDialectConfig } from 'kysely';
|
||||||
|
|
||||||
|
/** Type compatible with both [dyedgreen/deno-sqlite](https://github.com/dyedgreen/deno-sqlite) and [denodrivers/sqlite3](https://github.com/denodrivers/sqlite3). */
|
||||||
|
type DenoSqlite =
|
||||||
|
& {
|
||||||
|
close(): void;
|
||||||
|
changes: number;
|
||||||
|
lastInsertRowId: number;
|
||||||
|
}
|
||||||
|
& ({
|
||||||
|
queryEntries(sql: string, params: any): unknown[];
|
||||||
|
} | {
|
||||||
|
prepare(sql: string): {
|
||||||
|
all(...params: any): unknown[];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
interface DenoSqliteDialectConfig extends Omit<SqliteDialectConfig, 'database'> {
|
||||||
|
database: DenoSqlite | (() => Promise<DenoSqlite>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { DenoSqlite, DenoSqliteDialectConfig };
|
49
db/deno-sqlite-dialect.ts
Normal file
49
db/deno-sqlite-dialect.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/// The MIT License (MIT)
|
||||||
|
/// Copyright (c) 2023 Alex Gleason
|
||||||
|
/// Copyright (c) 2022 Sami Koskimäki
|
||||||
|
/// https://gitlab.com/soapbox-pub/kysely-deno-sqlite
|
||||||
|
|
||||||
|
import {
|
||||||
|
type DatabaseIntrospector,
|
||||||
|
type Dialect,
|
||||||
|
type DialectAdapter,
|
||||||
|
type Driver,
|
||||||
|
Kysely,
|
||||||
|
type QueryCompiler,
|
||||||
|
SqliteAdapter,
|
||||||
|
SqliteIntrospector,
|
||||||
|
SqliteQueryCompiler,
|
||||||
|
} from 'kysely';
|
||||||
|
|
||||||
|
import { DenoSqliteDriver } from './kysely-sqlite-driver.ts';
|
||||||
|
|
||||||
|
import type { DenoSqliteDialectConfig } from './deno-sqlite-dialect-config.ts';
|
||||||
|
|
||||||
|
class DenoSqliteDialect implements Dialect {
|
||||||
|
readonly #config: DenoSqliteDialectConfig;
|
||||||
|
|
||||||
|
constructor(config: DenoSqliteDialectConfig) {
|
||||||
|
this.#config = Object.freeze({ ...config });
|
||||||
|
}
|
||||||
|
|
||||||
|
createDriver(): Driver {
|
||||||
|
return new DenoSqliteDriver(this.#config);
|
||||||
|
}
|
||||||
|
|
||||||
|
createQueryCompiler(): QueryCompiler {
|
||||||
|
return new SqliteQueryCompiler();
|
||||||
|
}
|
||||||
|
|
||||||
|
createAdapter(): DialectAdapter {
|
||||||
|
return new SqliteAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
createIntrospector(db: Kysely<any>): DatabaseIntrospector {
|
||||||
|
return new SqliteIntrospector(db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { DenoSqliteDialect };
|
||||||
|
|
||||||
|
|
112
db/kysely-sqlite-driver.ts
Normal file
112
db/kysely-sqlite-driver.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/// The MIT License (MIT)
|
||||||
|
/// Copyright (c) 2023 Alex Gleason
|
||||||
|
/// Copyright (c) 2022 Sami Koskimäki
|
||||||
|
/// https://gitlab.com/soapbox-pub/kysely-deno-sqlite
|
||||||
|
|
||||||
|
import { CompiledQuery, type DatabaseConnection, type Driver, type QueryResult } from 'kysely';
|
||||||
|
|
||||||
|
import type { DenoSqlite, DenoSqliteDialectConfig } from './deno-sqlite-dialect-config.ts';
|
||||||
|
|
||||||
|
class DenoSqliteDriver implements Driver {
|
||||||
|
readonly #config: DenoSqliteDialectConfig;
|
||||||
|
readonly #connectionMutex = new ConnectionMutex();
|
||||||
|
|
||||||
|
#db?: DenoSqlite;
|
||||||
|
#connection?: DatabaseConnection;
|
||||||
|
|
||||||
|
constructor(config: DenoSqliteDialectConfig) {
|
||||||
|
this.#config = Object.freeze({ ...config });
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(): Promise<void> {
|
||||||
|
this.#db = typeof this.#config.database === 'function' ? await this.#config.database() : this.#config.database;
|
||||||
|
|
||||||
|
this.#connection = new DenoSqliteConnection(this.#db);
|
||||||
|
|
||||||
|
if (this.#config.onCreateConnection) {
|
||||||
|
await this.#config.onCreateConnection(this.#connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async acquireConnection(): Promise<DatabaseConnection> {
|
||||||
|
// SQLite only has one single connection. We use a mutex here to wait
|
||||||
|
// until the single connection has been released.
|
||||||
|
await this.#connectionMutex.lock();
|
||||||
|
return this.#connection!;
|
||||||
|
}
|
||||||
|
|
||||||
|
async beginTransaction(connection: DatabaseConnection): Promise<void> {
|
||||||
|
await connection.executeQuery(CompiledQuery.raw('begin'));
|
||||||
|
}
|
||||||
|
|
||||||
|
async commitTransaction(connection: DatabaseConnection): Promise<void> {
|
||||||
|
await connection.executeQuery(CompiledQuery.raw('commit'));
|
||||||
|
}
|
||||||
|
|
||||||
|
async rollbackTransaction(connection: DatabaseConnection): Promise<void> {
|
||||||
|
await connection.executeQuery(CompiledQuery.raw('rollback'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// deno-lint-ignore require-await
|
||||||
|
async releaseConnection(): Promise<void> {
|
||||||
|
this.#connectionMutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
// deno-lint-ignore require-await
|
||||||
|
async destroy(): Promise<void> {
|
||||||
|
this.#db?.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DenoSqliteConnection implements DatabaseConnection {
|
||||||
|
readonly #db: DenoSqlite;
|
||||||
|
|
||||||
|
constructor(db: DenoSqlite) {
|
||||||
|
this.#db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
executeQuery<O>({ sql, parameters }: CompiledQuery): Promise<QueryResult<O>> {
|
||||||
|
const rows = 'queryEntries' in this.#db
|
||||||
|
? this.#db.queryEntries(sql, parameters)
|
||||||
|
: this.#db.prepare(sql).all(...parameters);
|
||||||
|
|
||||||
|
const { changes, lastInsertRowId } = this.#db;
|
||||||
|
|
||||||
|
return Promise.resolve({
|
||||||
|
rows: rows as O[],
|
||||||
|
numAffectedRows: BigInt(changes),
|
||||||
|
insertId: BigInt(lastInsertRowId),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// deno-lint-ignore require-yield
|
||||||
|
async *streamQuery<R>(): AsyncIterableIterator<QueryResult<R>> {
|
||||||
|
throw new Error('Sqlite driver doesn\'t support streaming');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConnectionMutex {
|
||||||
|
#promise?: Promise<void>;
|
||||||
|
#resolve?: () => void;
|
||||||
|
|
||||||
|
async lock(): Promise<void> {
|
||||||
|
while (this.#promise) {
|
||||||
|
await this.#promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#promise = new Promise((resolve) => {
|
||||||
|
this.#resolve = resolve;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unlock(): void {
|
||||||
|
const resolve = this.#resolve;
|
||||||
|
|
||||||
|
this.#promise = undefined;
|
||||||
|
this.#resolve = undefined;
|
||||||
|
|
||||||
|
resolve?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { DenoSqliteDriver };
|
64
db/type.ts
Normal file
64
db/type.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { ColumnType, Generated, Insertable, Selectable, Updateable } from "kysely";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Code" TEXT,
|
||||||
|
"Date" TEXT,
|
||||||
|
"Close" INTEGER NOT NULL,
|
||||||
|
"Diff" INTEGER NOT NULL,
|
||||||
|
"Open" INTEGER NOT NULL,
|
||||||
|
"High" INTEGER NOT NULL,
|
||||||
|
"Low" INTEGER NOT NULL,
|
||||||
|
"Volume" INTEGER NOT NULL,
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface StockTable {
|
||||||
|
Code: string;
|
||||||
|
Date: string;
|
||||||
|
Close: number;
|
||||||
|
Diff: number;
|
||||||
|
Open: number;
|
||||||
|
High: number;
|
||||||
|
Low: number;
|
||||||
|
Volume: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KRXCorpTable{
|
||||||
|
Name: string;
|
||||||
|
/**
|
||||||
|
* PK
|
||||||
|
*/
|
||||||
|
Code: string;
|
||||||
|
|
||||||
|
Sector: string;
|
||||||
|
Product: string;
|
||||||
|
ListingDay: string;
|
||||||
|
ClosingMonth: string;
|
||||||
|
Representative: string;
|
||||||
|
Homepage: string;
|
||||||
|
AddressArea: string;
|
||||||
|
LastUpdate: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KOSPITable{
|
||||||
|
Name: string;
|
||||||
|
/**
|
||||||
|
* PK
|
||||||
|
*/
|
||||||
|
Code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KOSDAQTable{
|
||||||
|
Name: string;
|
||||||
|
/**
|
||||||
|
* PK
|
||||||
|
*/
|
||||||
|
Code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface Database {
|
||||||
|
stock: StockTable;
|
||||||
|
KRXCorp: KRXCorpTable;
|
||||||
|
KOSPI: KOSPITable;
|
||||||
|
KOSDAQ: KOSDAQTable;
|
||||||
|
}
|
@ -20,7 +20,9 @@
|
|||||||
"twind": "https://esm.sh/twind@0.16.19",
|
"twind": "https://esm.sh/twind@0.16.19",
|
||||||
"twind/": "https://esm.sh/twind@0.16.19/",
|
"twind/": "https://esm.sh/twind@0.16.19/",
|
||||||
"$std/": "https://deno.land/std@0.203.0/",
|
"$std/": "https://deno.land/std@0.203.0/",
|
||||||
"kysely": "npm:kysely@^0.25.0"
|
"kysely": "npm:kysely@^0.26.3",
|
||||||
|
"kysely/helpers/sqlite": "npm:kysely@^0.26.3/helpers/sqlite",
|
||||||
|
"sqlite": "https://deno.land/x/sqlite@v3.8/mod.ts"
|
||||||
},
|
},
|
||||||
"compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "preact" },
|
"compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "preact" },
|
||||||
"exclude": ["**/_fresh/*"]
|
"exclude": ["**/_fresh/*"]
|
||||||
|
36
fresh.gen.ts
36
fresh.gen.ts
@ -4,14 +4,16 @@
|
|||||||
|
|
||||||
import * as $0 from "./routes/_404.tsx";
|
import * as $0 from "./routes/_404.tsx";
|
||||||
import * as $1 from "./routes/_app.tsx";
|
import * as $1 from "./routes/_app.tsx";
|
||||||
import * as $2 from "./routes/api/joke.ts";
|
import * as $2 from "./routes/api/corps/[index].ts";
|
||||||
import * as $3 from "./routes/api/kosdaq.ts";
|
import * as $3 from "./routes/api/corps/index.ts";
|
||||||
import * as $4 from "./routes/api/kospi.ts";
|
import * as $4 from "./routes/api/joke.ts";
|
||||||
import * as $5 from "./routes/api/pages/[name].ts";
|
import * as $5 from "./routes/api/kosdaq.ts";
|
||||||
import * as $6 from "./routes/api/pages/index.ts";
|
import * as $6 from "./routes/api/kospi.ts";
|
||||||
import * as $7 from "./routes/greet/[name].tsx";
|
import * as $7 from "./routes/api/pages/[name].ts";
|
||||||
import * as $8 from "./routes/index.tsx";
|
import * as $8 from "./routes/api/pages/index.ts";
|
||||||
import * as $9 from "./routes/pages/[name].tsx";
|
import * as $9 from "./routes/greet/[name].tsx";
|
||||||
|
import * as $10 from "./routes/index.tsx";
|
||||||
|
import * as $11 from "./routes/pages/[name].tsx";
|
||||||
import * as $$0 from "./islands/Counter.tsx";
|
import * as $$0 from "./islands/Counter.tsx";
|
||||||
import * as $$1 from "./islands/Search.tsx";
|
import * as $$1 from "./islands/Search.tsx";
|
||||||
import * as $$2 from "./islands/StockList.tsx";
|
import * as $$2 from "./islands/StockList.tsx";
|
||||||
@ -20,14 +22,16 @@ const manifest = {
|
|||||||
routes: {
|
routes: {
|
||||||
"./routes/_404.tsx": $0,
|
"./routes/_404.tsx": $0,
|
||||||
"./routes/_app.tsx": $1,
|
"./routes/_app.tsx": $1,
|
||||||
"./routes/api/joke.ts": $2,
|
"./routes/api/corps/[index].ts": $2,
|
||||||
"./routes/api/kosdaq.ts": $3,
|
"./routes/api/corps/index.ts": $3,
|
||||||
"./routes/api/kospi.ts": $4,
|
"./routes/api/joke.ts": $4,
|
||||||
"./routes/api/pages/[name].ts": $5,
|
"./routes/api/kosdaq.ts": $5,
|
||||||
"./routes/api/pages/index.ts": $6,
|
"./routes/api/kospi.ts": $6,
|
||||||
"./routes/greet/[name].tsx": $7,
|
"./routes/api/pages/[name].ts": $7,
|
||||||
"./routes/index.tsx": $8,
|
"./routes/api/pages/index.ts": $8,
|
||||||
"./routes/pages/[name].tsx": $9,
|
"./routes/greet/[name].tsx": $9,
|
||||||
|
"./routes/index.tsx": $10,
|
||||||
|
"./routes/pages/[name].tsx": $11,
|
||||||
},
|
},
|
||||||
islands: {
|
islands: {
|
||||||
"./islands/Counter.tsx": $$0,
|
"./islands/Counter.tsx": $$0,
|
||||||
|
5
islands/Search.tsx
Normal file
5
islands/Search.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export default function Search(){
|
||||||
|
return <div>
|
||||||
|
<div>div</div>
|
||||||
|
</div>
|
||||||
|
}
|
35
routes/api/corps/[index].ts
Normal file
35
routes/api/corps/[index].ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { Handlers } from "$fresh/server.ts";
|
||||||
|
import { db } from "../../../db/db.ts";
|
||||||
|
import { jsonArrayFrom } from "kysely/helpers/sqlite";
|
||||||
|
|
||||||
|
export const handler: Handlers = {
|
||||||
|
async GET(req, ctx): Promise<Response> {
|
||||||
|
const headers = new Headers({
|
||||||
|
"content-type": "application/json"
|
||||||
|
});
|
||||||
|
const index = ctx.params.index;
|
||||||
|
const corp = await db.selectFrom("KRXCorp")
|
||||||
|
.selectAll([
|
||||||
|
"KRXCorp"
|
||||||
|
])
|
||||||
|
.select(eb=> [
|
||||||
|
jsonArrayFrom(eb.selectFrom("stock")
|
||||||
|
.select([
|
||||||
|
"stock.Close",
|
||||||
|
"stock.Open",
|
||||||
|
"stock.Low",
|
||||||
|
"stock.High",
|
||||||
|
"stock.Date",
|
||||||
|
"stock.Volume",
|
||||||
|
])
|
||||||
|
.where("Code", "=", index)
|
||||||
|
.orderBy("Date", "desc")
|
||||||
|
.limit(100)
|
||||||
|
).as("prices")]
|
||||||
|
)
|
||||||
|
.where("Code", "=", index)
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
|
return new Response(JSON.stringify(corp ?? null), {headers});
|
||||||
|
},
|
||||||
|
}
|
21
routes/api/corps/index.ts
Normal file
21
routes/api/corps/index.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Handlers } from "$fresh/server.ts";
|
||||||
|
import { db } from "../../../db/db.ts";
|
||||||
|
|
||||||
|
export const handler: Handlers = {
|
||||||
|
async GET(req, _ctx): Promise<Response> {
|
||||||
|
const headers = new Headers({
|
||||||
|
"content-type": "application/json"
|
||||||
|
});
|
||||||
|
const url = new URL(req.url);
|
||||||
|
const q = url.searchParams.get("q");
|
||||||
|
const name = url.searchParams.get("name");
|
||||||
|
const corps = await db.selectFrom("KRXCorp")
|
||||||
|
.selectAll([
|
||||||
|
"KRXCorp"
|
||||||
|
])
|
||||||
|
.$if(!!q, qb=> qb.where("Name", "like", "%"+q+"%"))
|
||||||
|
.$if(!!name, qb => qb.where("Name", "=", name))
|
||||||
|
.execute();
|
||||||
|
return new Response(JSON.stringify(corps), {headers});
|
||||||
|
},
|
||||||
|
}
|
@ -1,17 +1,17 @@
|
|||||||
import { Handlers } from "$fresh/server.ts";
|
import { Handlers } from "$fresh/server.ts";
|
||||||
import {DB} from "https://deno.land/x/sqlite/mod.ts";
|
import { db } from "../../db/db.ts";
|
||||||
|
|
||||||
export const handler: Handlers = {
|
export const handler: Handlers = {
|
||||||
async GET(req, _ctx): Promise<Response> {
|
async GET(req, _ctx): Promise<Response> {
|
||||||
const headers = new Headers({
|
const headers = new Headers({
|
||||||
"content-type": "application/json"
|
"content-type": "application/json"
|
||||||
});
|
});
|
||||||
const db = new DB("stock.db");
|
const rows = await db.selectFrom("KOSDAQ")
|
||||||
const conn = db.query("SELECT Code,Name FROM KOSDAQ");
|
.select([
|
||||||
const body = conn.map(row=>({
|
"Code",
|
||||||
code: row[0],
|
"Name"
|
||||||
name: row[1]
|
])
|
||||||
}))
|
.execute();
|
||||||
return new Response(JSON.stringify(body), {headers});
|
return new Response(JSON.stringify(rows), {headers});
|
||||||
},
|
},
|
||||||
}
|
}
|
@ -1,17 +1,17 @@
|
|||||||
import { Handlers } from "$fresh/server.ts";
|
import { Handlers } from "$fresh/server.ts";
|
||||||
import {DB} from "https://deno.land/x/sqlite/mod.ts";
|
import { db } from "../../db/db.ts";
|
||||||
|
|
||||||
export const handler: Handlers = {
|
export const handler: Handlers = {
|
||||||
async GET(req, _ctx): Promise<Response> {
|
async GET(req, _ctx): Promise<Response> {
|
||||||
const headers = new Headers({
|
const headers = new Headers({
|
||||||
"content-type": "application/json"
|
"content-type": "application/json"
|
||||||
});
|
});
|
||||||
const db = new DB("stock.db");
|
const rows = await db.selectFrom("KOSPI")
|
||||||
const conn = db.query("SELECT Code,Name FROM KOSPI");
|
.select([
|
||||||
const body = conn.map(row=>({
|
"Code",
|
||||||
code: row[0],
|
"Name"
|
||||||
name: row[1]
|
])
|
||||||
}))
|
.execute();
|
||||||
return new Response(JSON.stringify(body), {headers});
|
return new Response(JSON.stringify(rows), {headers});
|
||||||
},
|
},
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user