From 8e56b334ce67c304ab49c7df74dc9567c4811748 Mon Sep 17 00:00:00 2001 From: MK Date: Fri, 17 Oct 2025 22:20:06 +0800 Subject: [PATCH 1/3] feat: add tegg hacknews --- hackernews-tegg/.eslintrc | 5 + hackernews-tegg/.gitignore | 19 ++ hackernews-tegg/README.md | 33 +++ hackernews-tegg/app/controller/news.ts | 31 +++ hackernews-tegg/app/extend/filter.ts | 9 + hackernews-tegg/app/public/css/news.css | 203 ++++++++++++++++++ hackernews-tegg/app/public/favicon.png | Bin 0 -> 3113 bytes hackernews-tegg/app/router.ts | 10 + hackernews-tegg/app/service/News.ts | 76 +++++++ hackernews-tegg/app/view/layout/layout.tpl | 22 ++ hackernews-tegg/app/view/news/detail.tpl | 27 +++ hackernews-tegg/app/view/news/item.tpl | 16 ++ hackernews-tegg/app/view/news/list.tpl | 17 ++ hackernews-tegg/app/view/news/user.tpl | 23 ++ hackernews-tegg/config/config.default.ts | 42 ++++ hackernews-tegg/config/config.local.ts | 9 + hackernews-tegg/config/config.prod.ts | 9 + hackernews-tegg/config/plugin.ts | 6 + hackernews-tegg/package.json | 41 ++++ .../test/app/controller/news.test.ts | 28 +++ hackernews-tegg/test/app/service/News.test.ts | 21 ++ hackernews-tegg/tsconfig.json | 10 + 22 files changed, 657 insertions(+) create mode 100644 hackernews-tegg/.eslintrc create mode 100644 hackernews-tegg/.gitignore create mode 100644 hackernews-tegg/README.md create mode 100644 hackernews-tegg/app/controller/news.ts create mode 100644 hackernews-tegg/app/extend/filter.ts create mode 100644 hackernews-tegg/app/public/css/news.css create mode 100644 hackernews-tegg/app/public/favicon.png create mode 100644 hackernews-tegg/app/router.ts create mode 100644 hackernews-tegg/app/service/News.ts create mode 100644 hackernews-tegg/app/view/layout/layout.tpl create mode 100644 hackernews-tegg/app/view/news/detail.tpl create mode 100644 hackernews-tegg/app/view/news/item.tpl create mode 100644 hackernews-tegg/app/view/news/list.tpl create mode 100644 hackernews-tegg/app/view/news/user.tpl create mode 100644 hackernews-tegg/config/config.default.ts create mode 100644 hackernews-tegg/config/config.local.ts create mode 100644 hackernews-tegg/config/config.prod.ts create mode 100644 hackernews-tegg/config/plugin.ts create mode 100644 hackernews-tegg/package.json create mode 100644 hackernews-tegg/test/app/controller/news.test.ts create mode 100644 hackernews-tegg/test/app/service/News.test.ts create mode 100644 hackernews-tegg/tsconfig.json diff --git a/hackernews-tegg/.eslintrc b/hackernews-tegg/.eslintrc new file mode 100644 index 00000000..b4ea94ea --- /dev/null +++ b/hackernews-tegg/.eslintrc @@ -0,0 +1,5 @@ +{ + "root": true, + "extends": "eslint-config-egg/typescript" +} + diff --git a/hackernews-tegg/.gitignore b/hackernews-tegg/.gitignore new file mode 100644 index 00000000..59af3eb5 --- /dev/null +++ b/hackernews-tegg/.gitignore @@ -0,0 +1,19 @@ +logs/ +npm-debug.log +node_modules/ +coverage/ +.idea/ +run/ +logs/ +.DS_Store +.vscode +*.swp +*.lock +*.js + +app/**/*.js +test/**/*.js +config/**/*.js +app/**/*.map +test/**/*.map +config/**/*.map \ No newline at end of file diff --git a/hackernews-tegg/README.md b/hackernews-tegg/README.md new file mode 100644 index 00000000..62236e49 --- /dev/null +++ b/hackernews-tegg/README.md @@ -0,0 +1,33 @@ +# hackernews-async-ts + +[Hacker News](https://news.ycombinator.com/) showcase using typescript && egg + +## QuickStart + +### Development + +```bash +$ npm i +$ npm run dev +$ open http://localhost:7001/ +``` + +Don't tsc compile at development mode, if you had run `tsc` then you need to `npm run clean` before `npm run dev`. + +### Deploy + +```bash +$ npm run tsc +$ npm start +``` + +### Npm Scripts + +- Use `npm run lint` to check code style +- Use `npm test` to run unit test +- se `npm run clean` to clean compiled js at development mode once + +### Requirement + +- Node.js 16.x +- Typescript 4.x diff --git a/hackernews-tegg/app/controller/news.ts b/hackernews-tegg/app/controller/news.ts new file mode 100644 index 00000000..2bbd958d --- /dev/null +++ b/hackernews-tegg/app/controller/news.ts @@ -0,0 +1,31 @@ +import { Controller } from 'egg'; + +export default class NewsController extends Controller { + public async list() { + const { ctx, app } = this; + const pageSize = app.config.news.pageSize; + const page = parseInt(ctx.query.page, 10) || 1; + + const idList = await ctx.service.news.getTopStories(page); + + // get itemInfo parallel + const newsList = await Promise.all(idList.map(id => ctx.service.news.getItem(id))); + await ctx.render('news/list.tpl', { list: newsList, page, pageSize }); + } + + public async detail() { + const { ctx } = this; + const id = ctx.params.id; + const newsInfo = await ctx.service.news.getItem(id); + // get comment parallel + const commentList = await Promise.all(newsInfo.kids.map(_id => ctx.service.news.getItem(_id))); + await ctx.render('news/detail.tpl', { item: newsInfo, comments: commentList }); + } + + public async user() { + const { ctx } = this; + const id = ctx.params.id; + const userInfo = await ctx.service.news.getUser(id); + await ctx.render('news/user.tpl', { user: userInfo }); + } +} diff --git a/hackernews-tegg/app/extend/filter.ts b/hackernews-tegg/app/extend/filter.ts new file mode 100644 index 00000000..80813fce --- /dev/null +++ b/hackernews-tegg/app/extend/filter.ts @@ -0,0 +1,9 @@ +import moment from 'moment'; + +export function relativeTime(time) { + return moment(new Date(time * 1000)).fromNow(); +} + +export function domain(url) { + return url && url.split('/')[2]; +} diff --git a/hackernews-tegg/app/public/css/news.css b/hackernews-tegg/app/public/css/news.css new file mode 100644 index 00000000..b922c982 --- /dev/null +++ b/hackernews-tegg/app/public/css/news.css @@ -0,0 +1,203 @@ +body, +html { + font-family: Verdana; + font-size: 13px; + height: 100% +} +ul { + list-style-type: none; + padding: 0; + margin: 0 +} +a { + color: #000; + cursor: pointer; + text-decoration: none +} +#wrapper { + background-color: #f6f6ef; + width: 85%; + min-height: 80px; + margin: 0 auto +} +#header, +#wrapper { + position: relative +} +#header { + background-color: #f60; + height: 24px +} +#header h1 { + font-weight: 700; + font-size: 13px; + display: inline-block; + vertical-align: middle; + margin: 0 +} +#header .source { + color: #fff; + font-size: 11px; + position: absolute; + top: 4px; + right: 4px +} +#header .source a { + color: #fff +} +#header .source a:hover { + text-decoration: underline +} +#yc { + border: 1px solid #fff; + margin: 2px; + display: inline-block +} +#yc, +#yc img { + vertical-align: middle +} +.view { + position: absolute; + background-color: #f6f6ef; + width: 100%; + -webkit-transition: opacity .2s ease; + transition: opacity .2s ease; + box-sizing: border-box; + padding: 8px 20px +} +.view.v-enter, +.view.v-leave { + opacity: 0 +} +@media screen and (max-width: 700px) { + body, + html { + margin: 0 + } + #wrapper { + width: 100% + } +} +.news-view { + padding-left: 5px; + padding-right: 15px +} +.news-view.loading:before { + content: "Loading..."; + position: absolute; + top: 16px; + left: 20px +} +.news-view .nav { + padding: 10px 10px 10px 40px; + margin-top: 10px; + border-top: 2px solid #f60 +} +.news-view .nav a { + margin-right: 10px +} +.news-view .nav a:hover { + text-decoration: underline +} +.item { + padding: 2px 0 2px 40px; + position: relative; + -webkit-transition: background-color .2s ease; + transition: background-color .2s ease +} +.item p { + margin: 2px 0 +} +.item .index, +.item .title:visited { + color: #828282 +} +.item .index { + position: absolute; + width: 30px; + text-align: right; + left: 0; + top: 4px +} +.item .domain, +.item .subtext { + font-size: 11px; + color: #828282 +} +.item .domain a, +.item .subtext a { + color: #828282 +} +.item .subtext a:hover { + text-decoration: underline +} +.item-view .item { + padding-left: 0; + margin-bottom: 30px +} +.item-view .item .index { + display: none +} +.item-view .poll-options { + margin-left: 30px; + margin-bottom: 40px +} +.item-view .poll-options li { + margin: 12px 0 +} +.item-view .poll-options p { + margin: 8px 0 +} +.item-view .poll-options .subtext { + color: #828282; + font-size: 11px +} +.item-view .itemtext { + color: #828282; + margin-top: 0; + margin-bottom: 30px +} +.item-view .itemtext p { + margin: 10px 0 +} +.comhead { + font-size: 11px; + margin-bottom: 8px +} +.comhead, +.comhead a { + color: #828282 +} +.comhead a:hover { + text-decoration: underline +} +.comhead .toggle { + margin-right: 4px +} +.comment-content { + margin: 0 0 16px 24px; + word-wrap: break-word +} +.comment-content code { + white-space: pre-wrap +} +.child-comments { + margin: 8px 0 8px 22px +} +.user-view { + color: #828282 +} +.user-view li { + margin: 5px 0 +} +.user-view .label { + display: inline-block; + min-width: 60px +} +.user-view .about { + margin-top: 1em +} +.user-view .links a { + text-decoration: underline +} \ No newline at end of file diff --git a/hackernews-tegg/app/public/favicon.png b/hackernews-tegg/app/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..41d69cdc414faa424c7a12c783f8009f0fec2da8 GIT binary patch literal 3113 zcmZ8jZBLU|7;d3ZWE2q)Xlb$VFc1-~76A)Ipyf@V2$b@sWrea8kctAcQoxuknVnejbDQUT)ar7-EP*Yj9 z9H_(DMlcW`6@8>mG&rnVFzRn`q^8zL=rs2(-rBaTNlMDPCDm9~AePJ|Ko?7@A{E zi`C}YRTxiyj4oe-4u&7yh@1Y;d|MMjwuPXX>js!@iJ}f&J>-ganq^!Zo#2|*)Co8Wo4?lI0osj= zaM<92=EpbkU_HCeAAbCa1YcZx@U2+IhoP&|9t;8;Q%gBsEUV$`Un zX)cC8oZ%~E)%zPQ0VWlg-#?mU*#!Bk?8ZXBszxYP4ply8^x>Dz`@hNjt~dP=?ra0X zqcyf)9W>ZnF2McW`ABT=sr|YMgVobCRWOx_ki;u;oCVC?EUHVc`z?4fPt%C-WGejpaloW zLt-{;J`lhYckg2mx5hyMv*YCp0Us*>a8H_;Pt;)+&r>N{*x};*)lGh7!YU^)JIB|X zpavi%_6g6~WVsRMJCJqeho_M10(@@X5p!&%z;w;31jix)o-EeN264`SPO1i~hAk)! zIjpd_XMn%(8muOb$6K|??!j6X2k=7naH|0OmD||9KDm7z+Ff5CWzBUkO@hPW?ohw_ zK6w%>PN6>5!Q-yQ2jWntAz>~)?-GdF;LcsGb}%<4xWMZ*ntr+XimCzJS6+M$0hVr< z69Im*bo3RC&Dq!pIu7Ypj?#lp&C<#bEyNI?LmAu%3Dr}JIcHS z#EuowINstc%TqhlcIu~QiVE>{WoNuk-#f%2Cyc36D-*y*-74cv0wmQem)CaSKyhp8 z7Fy?Y)&nTrFsUku59Yd`{2r`-+n;J7iR;|H2=neu-4|m+EsGm+rAbV+&OaZ(ZRKk2f9-tbOvUg18ZpUS z;nV?v&x>{-2+EzVJ~YD{N-!&G4eB6-*E+y-h9GQYV4F8=jo3O*>(&Kc2!oJlX}ccG rwB+~k1jb$SF@uF0YU2~ecQe815J!PJps)e_<2Uqn2K+EP_$~Pl&{xhR literal 0 HcmV?d00001 diff --git a/hackernews-tegg/app/router.ts b/hackernews-tegg/app/router.ts new file mode 100644 index 00000000..cead0f3c --- /dev/null +++ b/hackernews-tegg/app/router.ts @@ -0,0 +1,10 @@ +import { Application } from 'egg'; + +export default (app: Application) => { + const { controller, router } = app; + + router.redirect('/', '/news'); + router.get('/news', controller.news.list); + router.get('/news/item/:id', controller.news.detail); + router.get('/news/user/:id', controller.news.user); +}; diff --git a/hackernews-tegg/app/service/News.ts b/hackernews-tegg/app/service/News.ts new file mode 100644 index 00000000..ffef8d5c --- /dev/null +++ b/hackernews-tegg/app/service/News.ts @@ -0,0 +1,76 @@ +import { Service } from 'egg'; + +export interface NewsItem { + id: number; + score: number; + time: number; + title: string; + type: string; + url: string; + descendants: number; + kids: number[]; + by: string; +} + +/** + * HackerNews Api Service + */ +export class HackerNews extends Service { + /** + * request hacker-news api + * @param api - Api name + * @param opts - urllib options + */ + public async request(api: string, opts?: any) { + const options = { + dataType: 'json', + timeout: '30s', + ...opts, + }; + + const result = await this.ctx.curl(`${this.config.news.serverUrl}/${api}`, options); + return result.data; + } + + /** + * get top story ids + * @param page - page number, 1-ase + * @param pageSize - page count + */ + public async getTopStories(page?: number, pageSize?: number): Promise { + page = page || 1; + const requestPageSize = pageSize ?? this.config.news.pageSize; + + try { + const result = await this.request('topstories.json', { + data: { + orderBy: '"$key"', + startAt: `"${requestPageSize * (page - 1)}"`, + endAt: `"${requestPageSize * page - 1}"`, + }, + }); + return Object.keys(result).map(key => result[key]); + } catch (e) { + this.ctx.logger.error(e); + return []; + } + } + + /** + * query item + * @param id - itemId + */ + public async getItem(id: number): Promise { + return await this.request(`item/${id}.json`); + } + + /** + * get user info + * @param id - userId + */ + public async getUser(id: number) { + return await this.request(`user/${id}.json`); + } +} + +export default HackerNews; diff --git a/hackernews-tegg/app/view/layout/layout.tpl b/hackernews-tegg/app/view/layout/layout.tpl new file mode 100644 index 00000000..aae4fb22 --- /dev/null +++ b/hackernews-tegg/app/view/layout/layout.tpl @@ -0,0 +1,22 @@ + + + + + + + + {% block title %}egg - HackerNews{% endblock %} + + +
+ + {% block content %}{% endblock %} +
+ + diff --git a/hackernews-tegg/app/view/news/detail.tpl b/hackernews-tegg/app/view/news/detail.tpl new file mode 100644 index 00000000..37a824b2 --- /dev/null +++ b/hackernews-tegg/app/view/news/detail.tpl @@ -0,0 +1,27 @@ +{% extends "../layout/layout.tpl" %} + +{% block content %} +
+ + {% include "./item.tpl" %} + + {% if comments.length > 0%} +
    +
    + [-] + {{ comment.by }} + {{ comment.time | relativeTime }} +
    +
    + {{ helper.shtml(comment.text) }} +
    + + {% endfor %} +
+ {% else %} +

No comments yet.

+ {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/hackernews-tegg/app/view/news/item.tpl b/hackernews-tegg/app/view/news/item.tpl new file mode 100644 index 00000000..0f42cd0f --- /dev/null +++ b/hackernews-tegg/app/view/news/item.tpl @@ -0,0 +1,16 @@ +
+ {{ index }}. +

+ {{ helper.shtml(item.title) }} + ({{ item.url | domain }}) +

+

+ + {{ item.score }} points by {{ item.by }} + + {{ item.time | relativeTime }} + + | {{ item.descendants }} comments + +

+
\ No newline at end of file diff --git a/hackernews-tegg/app/view/news/list.tpl b/hackernews-tegg/app/view/news/list.tpl new file mode 100644 index 00000000..7c466a13 --- /dev/null +++ b/hackernews-tegg/app/view/news/list.tpl @@ -0,0 +1,17 @@ +{% extends "../layout/layout.tpl" %} + +{% block content %} +
+ {% for item in list %} + {% set index = ((page-1) * pageSize + loop.index) %} + {% include "./item.tpl" %} + {% endfor %} + + +
+{% endblock %} diff --git a/hackernews-tegg/app/view/news/user.tpl b/hackernews-tegg/app/view/news/user.tpl new file mode 100644 index 00000000..fc9c0f5a --- /dev/null +++ b/hackernews-tegg/app/view/news/user.tpl @@ -0,0 +1,23 @@ +{% extends "../layout/layout.tpl" %} +{% block title %} + Profile: {{ user.id }} | egg - HackerNews +{% endblock %} +{% block content %} +
+
    +
  • user: {{ user.id }}
  • +
  • created: {{ user.created | relativeTime }}
  • +
  • karma: {{ user.karma }}
  • +
  • + about: +
    + {{ helper.shtml(user.about) }} +
    +
  • +
+ +
+{% endblock %} \ No newline at end of file diff --git a/hackernews-tegg/config/config.default.ts b/hackernews-tegg/config/config.default.ts new file mode 100644 index 00000000..64dfc022 --- /dev/null +++ b/hackernews-tegg/config/config.default.ts @@ -0,0 +1,42 @@ +import { EggAppConfig, PowerPartial } from 'egg'; +import * as fs from 'fs'; +import * as path from 'path'; + +// for config.{env}.ts +export type DefaultConfig = PowerPartial; + +// app special config scheme +export interface BizConfig { + sourceUrl: string; + news: { + pageSize: number; + serverUrl: string; + }; +} + +export default (appInfo: EggAppConfig) => { + const config = {} as PowerPartial & BizConfig; + + // app special config + config.sourceUrl = `https://github.com/eggjs/examples/tree/master/${appInfo.name}`; + config.news = { + pageSize: 30, + serverUrl: 'https://hacker-news.firebaseio.com/v0', + }; + + // override config from framework / plugin + config.keys = appInfo.name + '123456'; + + config.view = { + defaultViewEngine: 'nunjucks', + mapping: { + '.tpl': 'nunjucks', + }, + }; + + config.siteFile = { + '/favicon.ico': fs.readFileSync(path.join(appInfo.baseDir, 'app/public/favicon.png')), + }; + + return config; +}; diff --git a/hackernews-tegg/config/config.local.ts b/hackernews-tegg/config/config.local.ts new file mode 100644 index 00000000..c790a638 --- /dev/null +++ b/hackernews-tegg/config/config.local.ts @@ -0,0 +1,9 @@ +import { DefaultConfig } from './config.default'; + +export default () => { + const config: DefaultConfig = {}; + config.news = { + pageSize: 20, + }; + return config; +}; diff --git a/hackernews-tegg/config/config.prod.ts b/hackernews-tegg/config/config.prod.ts new file mode 100644 index 00000000..a1cb3447 --- /dev/null +++ b/hackernews-tegg/config/config.prod.ts @@ -0,0 +1,9 @@ +import { DefaultConfig } from './config.default'; + +export default () => { + const config: DefaultConfig = {}; + config.news = { + pageSize: 30, + }; + return config; +}; diff --git a/hackernews-tegg/config/plugin.ts b/hackernews-tegg/config/plugin.ts new file mode 100644 index 00000000..34d8cfe0 --- /dev/null +++ b/hackernews-tegg/config/plugin.ts @@ -0,0 +1,6 @@ +export default { + nunjucks: { + enable: true, + package: 'egg-view-nunjucks', + }, +}; diff --git a/hackernews-tegg/package.json b/hackernews-tegg/package.json new file mode 100644 index 00000000..237a5b99 --- /dev/null +++ b/hackernews-tegg/package.json @@ -0,0 +1,41 @@ +{ + "name": "hackernews-async-ts", + "version": "1.0.0", + "description": "hackernews showcase using typescript && egg", + "private": true, + "egg": { + "typescript": true + }, + "scripts": { + "start": "egg-scripts start", + "dev": "egg-bin dev", + "debug": "egg-bin debug", + "test-local": "egg-bin test", + "test": "npm run lint -- --fix && npm run test-local", + "cov": "egg-bin cov", + "tsc": "tsc -p tsconfig.json", + "ci": "npm run lint && npm run cov && npm run tsc", + "lint": "eslint .", + "clean": "tsc -b --clean" + }, + "dependencies": { + "egg": "^3.11.0", + "egg-scripts": "^2.17.0", + "egg-view-nunjucks": "^2.3.0", + "moment": "^2.22.0" + }, + "devDependencies": { + "@eggjs/tsconfig": "^1.1.0", + "@types/cheerio": "^0.22.1", + "@types/mocha": "^10.0.1", + "cheerio": "^1.0.0-rc.2", + "egg-bin": "^5.9.0", + "egg-mock": "^5.5.0", + "eslint": "^8.31.0", + "eslint-config-egg": "^12.1.0", + "typescript": "^4.9.4" + }, + "engines": { + "node": ">=16.0.0" + } +} diff --git a/hackernews-tegg/test/app/controller/news.test.ts b/hackernews-tegg/test/app/controller/news.test.ts new file mode 100644 index 00000000..ff2f9306 --- /dev/null +++ b/hackernews-tegg/test/app/controller/news.test.ts @@ -0,0 +1,28 @@ +import { strict as assert } from 'assert'; +import * as cheerio from 'cheerio'; +import { app } from 'egg-mock/bootstrap'; + +describe('test/app/controller/news.test.ts', () => { + it('should GET /news', async () => { + const result = await app.httpRequest().get('/news').expect(200); + const $ = cheerio.load(result.text); + const listItem = $('.news-view .item'); + assert(listItem.length === app.config.news.pageSize); + }); + + it('should GET /news/item/:id', async () => { + await app.httpRequest() + .get('/news/item/1') + // just a example, use regex to test part of dom string, but should be strong characteristic + .expect(/\/news\/item\/1/) + .expect(200); + }); + + it('should GET /news/user/:id', async () => { + await app.httpRequest() + .get('/news/user/activatedgeek') + // just a example, use regex to test part of dom string, but should be strong characteristic + .expect(/user:<\/span> activatedgeek/) + .expect(200); + }); +}); diff --git a/hackernews-tegg/test/app/service/News.test.ts b/hackernews-tegg/test/app/service/News.test.ts new file mode 100644 index 00000000..1d922b04 --- /dev/null +++ b/hackernews-tegg/test/app/service/News.test.ts @@ -0,0 +1,21 @@ +import { strict as assert } from 'assert'; +import { Context } from 'egg'; +import { app } from 'egg-mock/bootstrap'; + +describe('test/app/service/News.test.js', () => { + let ctx: Context; + + before(() => { + ctx = app.mockContext(); + }); + + it('getTopStories', async () => { + const list = await ctx.service.news.getTopStories(); + assert(list.length === 30); + }); + + it('getItem', async () => { + const item = await ctx.service.news.getItem(1); + assert(item.id && item.title && item.url); + }); +}); diff --git a/hackernews-tegg/tsconfig.json b/hackernews-tegg/tsconfig.json new file mode 100644 index 00000000..cff80205 --- /dev/null +++ b/hackernews-tegg/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@eggjs/tsconfig", + "compilerOptions": { + "declaration": false + }, + "exclude": [ + "app/public", + "app/views" + ] +} From 35a00b9d7d63df1ddc81036a87096970795227ef Mon Sep 17 00:00:00 2001 From: MK Date: Sat, 18 Oct 2025 13:05:47 +0800 Subject: [PATCH 2/3] faet: add tegg hacknews demo --- bin/test.sh | 2 + hackernews-async-ts-di/.autod.conf.js | 26 --- hackernews-async-ts-di/.gitignore | 71 ------ hackernews-async-ts-di/.vscode/settings.json | 13 -- hackernews-async-ts-di/README.md | 38 ---- .../app/controller/index.d.ts | 6 - hackernews-async-ts-di/app/controller/news.ts | 36 ---- hackernews-async-ts-di/app/extend/filter.ts | 7 - hackernews-async-ts-di/app/router.ts | 9 - .../app/service/HackerNews.ts | 81 ------- .../app/view/layout/layout.tpl | 22 -- hackernews-async-ts-di/config/config.ts | 26 --- .../config/defaultConfig.ts | 14 -- hackernews-async-ts-di/config/plugin.ts | 7 - hackernews-async-ts-di/package.json | 44 ---- .../test/app/controller/news.test.ts | 39 ---- .../test/app/service/HackerNews.test.ts | 32 --- hackernews-async-ts-di/tsconfig.json | 32 --- hackernews-async-ts-di/tslint.json | 43 ---- hackernews-async-ts/app/extend/filter.ts | 9 - hackernews-async-ts/app/public/css/news.css | 203 ------------------ hackernews-async-ts/app/public/favicon.png | Bin 3113 -> 0 bytes hackernews-async-ts/app/view/news/detail.tpl | 27 --- hackernews-async-ts/app/view/news/item.tpl | 16 -- hackernews-async-ts/app/view/news/list.tpl | 17 -- hackernews-async-ts/app/view/news/user.tpl | 23 -- hackernews-async-ts/config/config.default.ts | 42 ---- hackernews-async-ts/config/config.local.ts | 9 - hackernews-async-ts/config/config.prod.ts | 9 - hackernews-async-ts/package.json | 41 ---- hackernews-tegg/app/controller/news.ts | 6 +- hackernews-tegg/app/extend/filter.ts | 6 +- hackernews-tegg/app/service/News.ts | 14 +- hackernews-tegg/config/config.default.ts | 52 +++-- hackernews-tegg/config/config.local.ts | 12 +- hackernews-tegg/config/config.prod.ts | 12 +- hackernews-tegg/package.json | 40 ++-- .../test/app/controller/news.test.ts | 15 +- hackernews-tegg/test/app/service/News.test.ts | 10 +- .../typings/app/controller/index.d.ts | 12 ++ hackernews-tegg/typings/app/index.d.ts | 7 + .../typings/app/service/index.d.ts | 16 ++ .../.eslintrc | 0 .../.gitignore | 0 .../README.md | 9 +- .../app/controller/news.ts | 6 +- hackernews-typescript/app/extend/filter.ts | 9 + .../app/public/css/news.css | 0 .../app/public/favicon.png | Bin .../app/router.ts | 0 .../app/service/News.ts | 14 +- .../app/view/layout/layout.tpl | 0 .../app/view/news/detail.tpl | 0 .../app/view/news/item.tpl | 0 .../app/view/news/list.tpl | 0 .../app/view/news/user.tpl | 0 .../config/config.default.ts | 38 ++++ hackernews-typescript/config/config.local.ts | 7 + hackernews-typescript/config/config.prod.ts | 7 + .../config/plugin.ts | 0 hackernews-typescript/package.json | 45 ++++ .../test/app/controller/news.test.ts | 15 +- .../test/app/service/News.test.ts | 10 +- .../tsconfig.json | 0 .../typings/app/controller/index.d.ts | 12 ++ hackernews-typescript/typings/app/index.d.ts | 7 + .../typings/app/service/index.d.ts | 16 ++ 67 files changed, 288 insertions(+), 1053 deletions(-) delete mode 100644 hackernews-async-ts-di/.autod.conf.js delete mode 100644 hackernews-async-ts-di/.gitignore delete mode 100644 hackernews-async-ts-di/.vscode/settings.json delete mode 100644 hackernews-async-ts-di/README.md delete mode 100644 hackernews-async-ts-di/app/controller/index.d.ts delete mode 100644 hackernews-async-ts-di/app/controller/news.ts delete mode 100644 hackernews-async-ts-di/app/extend/filter.ts delete mode 100644 hackernews-async-ts-di/app/router.ts delete mode 100644 hackernews-async-ts-di/app/service/HackerNews.ts delete mode 100644 hackernews-async-ts-di/app/view/layout/layout.tpl delete mode 100644 hackernews-async-ts-di/config/config.ts delete mode 100644 hackernews-async-ts-di/config/defaultConfig.ts delete mode 100644 hackernews-async-ts-di/config/plugin.ts delete mode 100644 hackernews-async-ts-di/package.json delete mode 100644 hackernews-async-ts-di/test/app/controller/news.test.ts delete mode 100644 hackernews-async-ts-di/test/app/service/HackerNews.test.ts delete mode 100644 hackernews-async-ts-di/tsconfig.json delete mode 100644 hackernews-async-ts-di/tslint.json delete mode 100644 hackernews-async-ts/app/extend/filter.ts delete mode 100644 hackernews-async-ts/app/public/css/news.css delete mode 100644 hackernews-async-ts/app/public/favicon.png delete mode 100644 hackernews-async-ts/app/view/news/detail.tpl delete mode 100644 hackernews-async-ts/app/view/news/item.tpl delete mode 100644 hackernews-async-ts/app/view/news/list.tpl delete mode 100644 hackernews-async-ts/app/view/news/user.tpl delete mode 100644 hackernews-async-ts/config/config.default.ts delete mode 100644 hackernews-async-ts/config/config.local.ts delete mode 100644 hackernews-async-ts/config/config.prod.ts delete mode 100644 hackernews-async-ts/package.json create mode 100644 hackernews-tegg/typings/app/controller/index.d.ts create mode 100644 hackernews-tegg/typings/app/index.d.ts create mode 100644 hackernews-tegg/typings/app/service/index.d.ts rename {hackernews-async-ts => hackernews-typescript}/.eslintrc (100%) rename {hackernews-async-ts => hackernews-typescript}/.gitignore (100%) rename {hackernews-async-ts => hackernews-typescript}/README.md (75%) rename {hackernews-async-ts => hackernews-typescript}/app/controller/news.ts (84%) create mode 100644 hackernews-typescript/app/extend/filter.ts rename {hackernews-async-ts-di => hackernews-typescript}/app/public/css/news.css (100%) rename {hackernews-async-ts-di => hackernews-typescript}/app/public/favicon.png (100%) rename {hackernews-async-ts => hackernews-typescript}/app/router.ts (100%) rename {hackernews-async-ts => hackernews-typescript}/app/service/News.ts (80%) rename {hackernews-async-ts => hackernews-typescript}/app/view/layout/layout.tpl (100%) rename {hackernews-async-ts-di => hackernews-typescript}/app/view/news/detail.tpl (100%) rename {hackernews-async-ts-di => hackernews-typescript}/app/view/news/item.tpl (100%) rename {hackernews-async-ts-di => hackernews-typescript}/app/view/news/list.tpl (100%) rename {hackernews-async-ts-di => hackernews-typescript}/app/view/news/user.tpl (100%) create mode 100644 hackernews-typescript/config/config.default.ts create mode 100644 hackernews-typescript/config/config.local.ts create mode 100644 hackernews-typescript/config/config.prod.ts rename {hackernews-async-ts => hackernews-typescript}/config/plugin.ts (100%) create mode 100644 hackernews-typescript/package.json rename {hackernews-async-ts => hackernews-typescript}/test/app/controller/news.test.ts (54%) rename {hackernews-async-ts => hackernews-typescript}/test/app/service/News.test.ts (63%) rename {hackernews-async-ts => hackernews-typescript}/tsconfig.json (100%) create mode 100644 hackernews-typescript/typings/app/controller/index.d.ts create mode 100644 hackernews-typescript/typings/app/index.d.ts create mode 100644 hackernews-typescript/typings/app/service/index.d.ts diff --git a/bin/test.sh b/bin/test.sh index 3e28d962..07dca659 100755 --- a/bin/test.sh +++ b/bin/test.sh @@ -15,3 +15,5 @@ test() { test body-parser-example test helloworld test hello-tegg +test hackernews-typescript +test hackernews-tegg diff --git a/hackernews-async-ts-di/.autod.conf.js b/hackernews-async-ts-di/.autod.conf.js deleted file mode 100644 index 4d1fb4ad..00000000 --- a/hackernews-async-ts-di/.autod.conf.js +++ /dev/null @@ -1,26 +0,0 @@ -'ues strict'; - -module.exports = { - write: true, - plugin: 'autod-egg', - prefix: '^', - devprefix: '^', - exclude: [ - 'test/fixtures', - ], - dep: [ - 'egg', - ], - devdep: [ - 'autod', - 'autod-egg', - 'egg-bin', - ], - keep: [ - 'tslib', - 'typescript', - ], - semver: [ - ], - test: 'scripts', -}; diff --git a/hackernews-async-ts-di/.gitignore b/hackernews-async-ts-di/.gitignore deleted file mode 100644 index faf57fa0..00000000 --- a/hackernews-async-ts-di/.gitignore +++ /dev/null @@ -1,71 +0,0 @@ -app/**/*.js -test/**/*.js -config/**/*.js -*.map -run -logs - -# Created by https://www.gitignore.io/api/node - -### Node ### -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Typescript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env - - -# End of https://www.gitignore.io/api/node diff --git a/hackernews-async-ts-di/.vscode/settings.json b/hackernews-async-ts-di/.vscode/settings.json deleted file mode 100644 index db221e08..00000000 --- a/hackernews-async-ts-di/.vscode/settings.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "files.exclude": { - "USE_GITIGNORE": true, - "**/*.js": { - "when": "$(basename).ts" - }, - "**/*.map": true, - "run": true, - "logs": true, - "out": true, - "node_modules": true - } -} diff --git a/hackernews-async-ts-di/README.md b/hackernews-async-ts-di/README.md deleted file mode 100644 index dded0eb1..00000000 --- a/hackernews-async-ts-di/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# egg-example-hackernews-async - -[Hacker News](https://news.ycombinator.com/) showcase using async/await for egg - -## QuickStart - -### Development -```shell -$ npm install -$ npm run tsc:w -$ npm run dev -$ open http://localhost:7001/ -``` - -### Deploy - -Use `EGG_SERVER_ENV=prod` to enable prod mode - -```shell -$ EGG_SERVER_ENV=prod npm start -``` - -### Npm Scripts - -- Use `npm run autod` to auto detect dependencies upgrade -- Use `npm run lint` to check code style -- Use `npm test` to run unit test - -### Requirement - -Please ensure your node version is `>=7.6.0` for async await support without flag. If your node version is `>=7.0.0 < 7.6.0`, you can run npm scripts with harmony flag - -```shell -# start server -npm run dev -- --harmony-async-await -# run test cases -npm run test-local -- --harmony-async-await -``` diff --git a/hackernews-async-ts-di/app/controller/index.d.ts b/hackernews-async-ts-di/app/controller/index.d.ts deleted file mode 100644 index 0b8bebb8..00000000 --- a/hackernews-async-ts-di/app/controller/index.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -import NewsController from './news'; -declare module 'egg' { - export interface IController { - news: NewsController; - } -} diff --git a/hackernews-async-ts-di/app/controller/news.ts b/hackernews-async-ts-di/app/controller/news.ts deleted file mode 100644 index 16e7f7a2..00000000 --- a/hackernews-async-ts-di/app/controller/news.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Controller } from 'egg'; -import { inject } from 'egg-di'; -import { HackerNews } from '../service/HackerNews'; - -export default class NewsController extends Controller { - @inject() - private readonly hackerNews: HackerNews; - - public async list() { - const { ctx, app } = this; - const pageSize = app.config.news.pageSize; - const page = parseInt(ctx.query.page, 10) || 1; - - const idList = await this.hackerNews.getTopStories(page); - - // get itemInfo parallel - const newsList = await Promise.all(idList.map((id) => this.hackerNews.getItem(id))); - await ctx.render('news/list.tpl', { list: newsList, page, pageSize }); - } - - public async detail() { - const { ctx } = this; - const id = ctx.params.id; - const newsInfo = await this.hackerNews.getItem(id); - // get comment parallel - const commentList = await Promise.all(newsInfo.kids.map((_id) => this.hackerNews.getItem(_id))); - await ctx.render('news/detail.tpl', { item: newsInfo, comments: commentList }); - } - - public async user() { - const { ctx } = this; - const id = ctx.params.id; - const userInfo = await this.hackerNews.getUser(id); - await ctx.render('news/user.tpl', { user: userInfo }); - } -} diff --git a/hackernews-async-ts-di/app/extend/filter.ts b/hackernews-async-ts-di/app/extend/filter.ts deleted file mode 100644 index e74123e1..00000000 --- a/hackernews-async-ts-di/app/extend/filter.ts +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -import * as moment from 'moment'; - -exports.relativeTime = (time) => moment(new Date(time * 1000)).fromNow(); - -exports.domain = (url) => url && url.split('/')[2]; diff --git a/hackernews-async-ts-di/app/router.ts b/hackernews-async-ts-di/app/router.ts deleted file mode 100644 index 4d5d3113..00000000 --- a/hackernews-async-ts-di/app/router.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Application } from 'egg'; - -export default (app: Application) => { - const controller = app.controller; - app.redirect('/', '/news'); - app.router.get('/news', controller.news.list); - app.router.get('/news/item/:id', controller.news.detail); - app.router.get('/news/user/:id', controller.news.user); -}; diff --git a/hackernews-async-ts-di/app/service/HackerNews.ts b/hackernews-async-ts-di/app/service/HackerNews.ts deleted file mode 100644 index 30bbfc27..00000000 --- a/hackernews-async-ts-di/app/service/HackerNews.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Service } from 'egg'; -import { Context } from 'egg-di'; - -export interface NewsItem { - id: number; - score: number; - time: number; - title: string; - type: string; - url: string; - descendants: number; - kids: number[]; - by: string; -} - -/** - * HackerNews Api Service - */ -@Context -export class HackerNews extends Service { - getConfig() { - return this.app.config.news; - } - - /** - * request hacker-news api - * @param api - Api name - * @param opts - urllib options - */ - public async request(api: string, opts?: object) { - const options = Object.assign({ - dataType: 'json', - timeout: ['30s', '30s'], - }, opts); - - const result = await this.ctx.curl(`${this.getConfig().serverUrl}/${api}`, options); - return result.data; - } - - /** - * get top story ids - * @param page - page number, 1-ase - * @param pageSize - page count - */ - public async getTopStories(page?: number, pageSize?: number): Promise { - page = page || 1; - pageSize = pageSize || this.getConfig().pageSize; - - try { - const result = await this.request('topstories.json', { - data: { - orderBy: '"$key"', - startAt: `"${pageSize * (page - 1)}"`, - endAt: `"${pageSize * page - 1}"`, - }, - }); - return Object.keys(result).map((key) => result[key]); - } catch (e) { - this.ctx.logger.error(e); - return []; - } - } - - /** - * query item - * @param id - itemId - */ - public async getItem(id: number): Promise { - return await this.request(`item/${id}.json`); - } - - /** - * get user info - * @param id - userId - */ - public async getUser(id: number) { - return await this.request(`user/${id}.json`); - } -} - -export default HackerNews; diff --git a/hackernews-async-ts-di/app/view/layout/layout.tpl b/hackernews-async-ts-di/app/view/layout/layout.tpl deleted file mode 100644 index d9fcc3dc..00000000 --- a/hackernews-async-ts-di/app/view/layout/layout.tpl +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - {% block title %}egg - HackerNews{% endblock %} - - -
- - {% block content %}{% endblock %} -
- - diff --git a/hackernews-async-ts-di/config/config.ts b/hackernews-async-ts-di/config/config.ts deleted file mode 100644 index f15ec1c0..00000000 --- a/hackernews-async-ts-di/config/config.ts +++ /dev/null @@ -1,26 +0,0 @@ -'use strict'; -import { EggAppConfig } from 'egg'; -import * as fs from 'fs'; -import * as path from 'path'; -import 'source-map-support/register'; -import defaultConfig from './defaultConfig'; - -export default (appInfo: EggAppConfig) => { - const config: any = {}; - - // should change to your own - config.keys = appInfo.name + '123456'; - - config.siteFile = { - '/favicon.ico': fs.readFileSync(path.join(appInfo.baseDir, 'app/public/favicon.png')), - }; - - config.view = { - defaultViewEngine: 'nunjucks', - mapping: { - '.tpl': 'nunjucks', - }, - }; - - return { ...config, ...defaultConfig }; -}; diff --git a/hackernews-async-ts-di/config/defaultConfig.ts b/hackernews-async-ts-di/config/defaultConfig.ts deleted file mode 100644 index e6279b5b..00000000 --- a/hackernews-async-ts-di/config/defaultConfig.ts +++ /dev/null @@ -1,14 +0,0 @@ -export class DefaultConfig { - news = { - pageSize: 30, - serverUrl: 'https://hacker-news.firebaseio.com/v0', - }; -}; - -export default new DefaultConfig(); - -declare module 'egg' { - export interface Application { - config: EggAppConfig & DefaultConfig; - } -} diff --git a/hackernews-async-ts-di/config/plugin.ts b/hackernews-async-ts-di/config/plugin.ts deleted file mode 100644 index 31c30468..00000000 --- a/hackernews-async-ts-di/config/plugin.ts +++ /dev/null @@ -1,7 +0,0 @@ -exports.static = true; - -exports.nunjucks = { - enable: true, - package: 'egg-view-nunjucks', -}; - diff --git a/hackernews-async-ts-di/package.json b/hackernews-async-ts-di/package.json deleted file mode 100644 index 6de79cb8..00000000 --- a/hackernews-async-ts-di/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "egg-example-hackernews-async-ts", - "version": "1.0.0", - "description": "hackernews showcase using async/await for egg", - "private": true, - "dependencies": { - "egg": "^2.0.0", - "egg-di": "^1.0.0", - "egg-view-nunjucks": "^2.1.4", - "moment": "^2.19.4", - "source-map-support": "^0.5.0", - "tslib": "^1.8.1", - "typescript": "^2.6.2" - }, - "devDependencies": { - "@types/cheerio": "^0.22.1", - "@types/mocha": "^2.2.40", - "@types/node": "^7.0.12", - "@types/supertest": "^2.0.0", - "autod": "^3.0.1", - "autod-egg": "^1.1.0", - "cheerio": "^1.0.0-rc.2", - "egg-bin": "^4.3.7", - "egg-mock": "^3.14.0", - "rimraf": "^2.6.1", - "tslint": "^4.0.0" - }, - "engines": { - "node": ">=8.9.0" - }, - "scripts": { - "clean": "rimraf app/**/*.{js,map} test/**/*.{js,map} config/**/*.{js,map}", - "tsc": "tsc -p tsconfig.json", - "tsc:w": "tsc -p tsconfig.json -w", - "debug": "egg-bin debug", - "dev": "egg-bin dev", - "test": "npm run tsc && npm run test-local", - "test-local": "egg-bin test", - "cov": "egg-bin cov", - "lint": "tslint .", - "ci": "npm run lint && npm run cov", - "autod": "autod" - } -} diff --git a/hackernews-async-ts-di/test/app/controller/news.test.ts b/hackernews-async-ts-di/test/app/controller/news.test.ts deleted file mode 100644 index df93b568..00000000 --- a/hackernews-async-ts-di/test/app/controller/news.test.ts +++ /dev/null @@ -1,39 +0,0 @@ -'use strict'; - -import * as assert from 'assert'; -import * as cheerio from 'cheerio'; -import mm from 'egg-mock'; - -describe('test/app/controller/news.test.ts', () => { - const app = mm.app(); - before(async () => { - await app.ready(); - }); - - after(() => app.close()); - - afterEach(mm.restore); - - it('should GET /news', async () => { - const result = await app.httpRequest().get('/news').expect(200); - const $ = cheerio.load(result.text); - const listItem = $('.news-view .item'); - assert(listItem.length === app.config.news.pageSize); - }); - - it('should GET /news/item/:id', async () => { - await app.httpRequest() - .get('/news/item/1') - // just a example, use regex to test part of dom string, but should be strong characteristic - .expect(/\/news\/item\/1/) - .expect(200); - }); - - it('should GET /news/user/:id', async () => { - await app.httpRequest() - .get('/news/user/activatedgeek') - // just a example, use regex to test part of dom string, but should be strong characteristic - .expect(/user:<\/span> activatedgeek/) - .expect(200); - }); -}); diff --git a/hackernews-async-ts-di/test/app/service/HackerNews.test.ts b/hackernews-async-ts-di/test/app/service/HackerNews.test.ts deleted file mode 100644 index a183c477..00000000 --- a/hackernews-async-ts-di/test/app/service/HackerNews.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; - -import * as assert from 'assert'; -import { Context } from 'egg'; -import { getComponent } from 'egg-di'; -import mm from 'egg-mock'; -import { HackerNews } from '../../../app/service/HackerNews'; - -describe('test/app/service/HackerNews.test.js', () => { - const app = mm.app(); - let ctx: Context; - let hackerNews: HackerNews; - - before(async () => { - await app.ready(); - ctx = app.mockContext(); - hackerNews = getComponent(HackerNews, ctx); - }); - - after(() => app.close()); - afterEach(mm.restore); - - it('getTopStories', async () => { - const list = await hackerNews.getTopStories(); - assert(list.length === 30); - }); - - it('getItem', async () => { - const item = await hackerNews.getItem(1); - assert(item.id && item.title && item.url); - }); -}); diff --git a/hackernews-async-ts-di/tsconfig.json b/hackernews-async-ts-di/tsconfig.json deleted file mode 100644 index 05d10e45..00000000 --- a/hackernews-async-ts-di/tsconfig.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "compileOnSave": true, - "compilerOptions": { - "target": "es2017", - "module": "commonjs", - "noImplicitAny": false, - "experimentalDecorators": true, - "emitDecoratorMetadata": true, - "charset": "utf8", - "allowJs": false, - "pretty": true, - "noEmitOnError": false, - "noUnusedLocals": true, - "noUnusedParameters": true, - "allowUnreachableCode": false, - "allowUnusedLabels": false, - "noFallthroughCasesInSwitch": true, - "skipLibCheck": true, - "skipDefaultLibCheck": true, - "inlineSourceMap": true, - "importHelpers": true - }, - "include": [ - "app/**/*", - "config/**/*", - "test/**/*.ts" - ], - "exclude": [ - "app/public", - "app/views" - ] -} diff --git a/hackernews-async-ts-di/tslint.json b/hackernews-async-ts-di/tslint.json deleted file mode 100644 index 73221c9f..00000000 --- a/hackernews-async-ts-di/tslint.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "extends": "tslint:latest", - "rules": { - "quotemark": [ - true, - "single", - "jsx-double" - ], - "no-console": [ - true, - "dir", - "log", - "error", - "warn" - ], - "space-before-function-paren": false, - "interface-name": [ - true, - "never-prefix" - ], - "adjacent-overload-signatures": true, - "member-access": [ - false - ], - "member-ordering": [ - true, - { - "order": "fields-first" - } - ], - "object-literal-sort-keys": false, - "max-classes-per-file": [ - true, - 10 - ], - "variable-name": [ - true, - "allow-leading-underscore" - ], - "align": [true, "statements"] - } -} - diff --git a/hackernews-async-ts/app/extend/filter.ts b/hackernews-async-ts/app/extend/filter.ts deleted file mode 100644 index 80813fce..00000000 --- a/hackernews-async-ts/app/extend/filter.ts +++ /dev/null @@ -1,9 +0,0 @@ -import moment from 'moment'; - -export function relativeTime(time) { - return moment(new Date(time * 1000)).fromNow(); -} - -export function domain(url) { - return url && url.split('/')[2]; -} diff --git a/hackernews-async-ts/app/public/css/news.css b/hackernews-async-ts/app/public/css/news.css deleted file mode 100644 index b922c982..00000000 --- a/hackernews-async-ts/app/public/css/news.css +++ /dev/null @@ -1,203 +0,0 @@ -body, -html { - font-family: Verdana; - font-size: 13px; - height: 100% -} -ul { - list-style-type: none; - padding: 0; - margin: 0 -} -a { - color: #000; - cursor: pointer; - text-decoration: none -} -#wrapper { - background-color: #f6f6ef; - width: 85%; - min-height: 80px; - margin: 0 auto -} -#header, -#wrapper { - position: relative -} -#header { - background-color: #f60; - height: 24px -} -#header h1 { - font-weight: 700; - font-size: 13px; - display: inline-block; - vertical-align: middle; - margin: 0 -} -#header .source { - color: #fff; - font-size: 11px; - position: absolute; - top: 4px; - right: 4px -} -#header .source a { - color: #fff -} -#header .source a:hover { - text-decoration: underline -} -#yc { - border: 1px solid #fff; - margin: 2px; - display: inline-block -} -#yc, -#yc img { - vertical-align: middle -} -.view { - position: absolute; - background-color: #f6f6ef; - width: 100%; - -webkit-transition: opacity .2s ease; - transition: opacity .2s ease; - box-sizing: border-box; - padding: 8px 20px -} -.view.v-enter, -.view.v-leave { - opacity: 0 -} -@media screen and (max-width: 700px) { - body, - html { - margin: 0 - } - #wrapper { - width: 100% - } -} -.news-view { - padding-left: 5px; - padding-right: 15px -} -.news-view.loading:before { - content: "Loading..."; - position: absolute; - top: 16px; - left: 20px -} -.news-view .nav { - padding: 10px 10px 10px 40px; - margin-top: 10px; - border-top: 2px solid #f60 -} -.news-view .nav a { - margin-right: 10px -} -.news-view .nav a:hover { - text-decoration: underline -} -.item { - padding: 2px 0 2px 40px; - position: relative; - -webkit-transition: background-color .2s ease; - transition: background-color .2s ease -} -.item p { - margin: 2px 0 -} -.item .index, -.item .title:visited { - color: #828282 -} -.item .index { - position: absolute; - width: 30px; - text-align: right; - left: 0; - top: 4px -} -.item .domain, -.item .subtext { - font-size: 11px; - color: #828282 -} -.item .domain a, -.item .subtext a { - color: #828282 -} -.item .subtext a:hover { - text-decoration: underline -} -.item-view .item { - padding-left: 0; - margin-bottom: 30px -} -.item-view .item .index { - display: none -} -.item-view .poll-options { - margin-left: 30px; - margin-bottom: 40px -} -.item-view .poll-options li { - margin: 12px 0 -} -.item-view .poll-options p { - margin: 8px 0 -} -.item-view .poll-options .subtext { - color: #828282; - font-size: 11px -} -.item-view .itemtext { - color: #828282; - margin-top: 0; - margin-bottom: 30px -} -.item-view .itemtext p { - margin: 10px 0 -} -.comhead { - font-size: 11px; - margin-bottom: 8px -} -.comhead, -.comhead a { - color: #828282 -} -.comhead a:hover { - text-decoration: underline -} -.comhead .toggle { - margin-right: 4px -} -.comment-content { - margin: 0 0 16px 24px; - word-wrap: break-word -} -.comment-content code { - white-space: pre-wrap -} -.child-comments { - margin: 8px 0 8px 22px -} -.user-view { - color: #828282 -} -.user-view li { - margin: 5px 0 -} -.user-view .label { - display: inline-block; - min-width: 60px -} -.user-view .about { - margin-top: 1em -} -.user-view .links a { - text-decoration: underline -} \ No newline at end of file diff --git a/hackernews-async-ts/app/public/favicon.png b/hackernews-async-ts/app/public/favicon.png deleted file mode 100644 index 41d69cdc414faa424c7a12c783f8009f0fec2da8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3113 zcmZ8jZBLU|7;d3ZWE2q)Xlb$VFc1-~76A)Ipyf@V2$b@sWrea8kctAcQoxuknVnejbDQUT)ar7-EP*Yj9 z9H_(DMlcW`6@8>mG&rnVFzRn`q^8zL=rs2(-rBaTNlMDPCDm9~AePJ|Ko?7@A{E zi`C}YRTxiyj4oe-4u&7yh@1Y;d|MMjwuPXX>js!@iJ}f&J>-ganq^!Zo#2|*)Co8Wo4?lI0osj= zaM<92=EpbkU_HCeAAbCa1YcZx@U2+IhoP&|9t;8;Q%gBsEUV$`Un zX)cC8oZ%~E)%zPQ0VWlg-#?mU*#!Bk?8ZXBszxYP4ply8^x>Dz`@hNjt~dP=?ra0X zqcyf)9W>ZnF2McW`ABT=sr|YMgVobCRWOx_ki;u;oCVC?EUHVc`z?4fPt%C-WGejpaloW zLt-{;J`lhYckg2mx5hyMv*YCp0Us*>a8H_;Pt;)+&r>N{*x};*)lGh7!YU^)JIB|X zpavi%_6g6~WVsRMJCJqeho_M10(@@X5p!&%z;w;31jix)o-EeN264`SPO1i~hAk)! zIjpd_XMn%(8muOb$6K|??!j6X2k=7naH|0OmD||9KDm7z+Ff5CWzBUkO@hPW?ohw_ zK6w%>PN6>5!Q-yQ2jWntAz>~)?-GdF;LcsGb}%<4xWMZ*ntr+XimCzJS6+M$0hVr< z69Im*bo3RC&Dq!pIu7Ypj?#lp&C<#bEyNI?LmAu%3Dr}JIcHS z#EuowINstc%TqhlcIu~QiVE>{WoNuk-#f%2Cyc36D-*y*-74cv0wmQem)CaSKyhp8 z7Fy?Y)&nTrFsUku59Yd`{2r`-+n;J7iR;|H2=neu-4|m+EsGm+rAbV+&OaZ(ZRKk2f9-tbOvUg18ZpUS z;nV?v&x>{-2+EzVJ~YD{N-!&G4eB6-*E+y-h9GQYV4F8=jo3O*>(&Kc2!oJlX}ccG rwB+~k1jb$SF@uF0YU2~ecQe815J!PJps)e_<2Uqn2K+EP_$~Pl&{xhR diff --git a/hackernews-async-ts/app/view/news/detail.tpl b/hackernews-async-ts/app/view/news/detail.tpl deleted file mode 100644 index 37a824b2..00000000 --- a/hackernews-async-ts/app/view/news/detail.tpl +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "../layout/layout.tpl" %} - -{% block content %} -
- - {% include "./item.tpl" %} - - {% if comments.length > 0%} -
    -
    - [-] - {{ comment.by }} - {{ comment.time | relativeTime }} -
    -
    - {{ helper.shtml(comment.text) }} -
    - - {% endfor %} -
- {% else %} -

No comments yet.

- {% endif %} -
-{% endblock %} \ No newline at end of file diff --git a/hackernews-async-ts/app/view/news/item.tpl b/hackernews-async-ts/app/view/news/item.tpl deleted file mode 100644 index 0f42cd0f..00000000 --- a/hackernews-async-ts/app/view/news/item.tpl +++ /dev/null @@ -1,16 +0,0 @@ -
- {{ index }}. -

- {{ helper.shtml(item.title) }} - ({{ item.url | domain }}) -

-

- - {{ item.score }} points by {{ item.by }} - - {{ item.time | relativeTime }} - - | {{ item.descendants }} comments - -

-
\ No newline at end of file diff --git a/hackernews-async-ts/app/view/news/list.tpl b/hackernews-async-ts/app/view/news/list.tpl deleted file mode 100644 index 7c466a13..00000000 --- a/hackernews-async-ts/app/view/news/list.tpl +++ /dev/null @@ -1,17 +0,0 @@ -{% extends "../layout/layout.tpl" %} - -{% block content %} -
- {% for item in list %} - {% set index = ((page-1) * pageSize + loop.index) %} - {% include "./item.tpl" %} - {% endfor %} - - -
-{% endblock %} diff --git a/hackernews-async-ts/app/view/news/user.tpl b/hackernews-async-ts/app/view/news/user.tpl deleted file mode 100644 index fc9c0f5a..00000000 --- a/hackernews-async-ts/app/view/news/user.tpl +++ /dev/null @@ -1,23 +0,0 @@ -{% extends "../layout/layout.tpl" %} -{% block title %} - Profile: {{ user.id }} | egg - HackerNews -{% endblock %} -{% block content %} -
-
    -
  • user: {{ user.id }}
  • -
  • created: {{ user.created | relativeTime }}
  • -
  • karma: {{ user.karma }}
  • -
  • - about: -
    - {{ helper.shtml(user.about) }} -
    -
  • -
- -
-{% endblock %} \ No newline at end of file diff --git a/hackernews-async-ts/config/config.default.ts b/hackernews-async-ts/config/config.default.ts deleted file mode 100644 index 64dfc022..00000000 --- a/hackernews-async-ts/config/config.default.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { EggAppConfig, PowerPartial } from 'egg'; -import * as fs from 'fs'; -import * as path from 'path'; - -// for config.{env}.ts -export type DefaultConfig = PowerPartial; - -// app special config scheme -export interface BizConfig { - sourceUrl: string; - news: { - pageSize: number; - serverUrl: string; - }; -} - -export default (appInfo: EggAppConfig) => { - const config = {} as PowerPartial & BizConfig; - - // app special config - config.sourceUrl = `https://github.com/eggjs/examples/tree/master/${appInfo.name}`; - config.news = { - pageSize: 30, - serverUrl: 'https://hacker-news.firebaseio.com/v0', - }; - - // override config from framework / plugin - config.keys = appInfo.name + '123456'; - - config.view = { - defaultViewEngine: 'nunjucks', - mapping: { - '.tpl': 'nunjucks', - }, - }; - - config.siteFile = { - '/favicon.ico': fs.readFileSync(path.join(appInfo.baseDir, 'app/public/favicon.png')), - }; - - return config; -}; diff --git a/hackernews-async-ts/config/config.local.ts b/hackernews-async-ts/config/config.local.ts deleted file mode 100644 index c790a638..00000000 --- a/hackernews-async-ts/config/config.local.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { DefaultConfig } from './config.default'; - -export default () => { - const config: DefaultConfig = {}; - config.news = { - pageSize: 20, - }; - return config; -}; diff --git a/hackernews-async-ts/config/config.prod.ts b/hackernews-async-ts/config/config.prod.ts deleted file mode 100644 index a1cb3447..00000000 --- a/hackernews-async-ts/config/config.prod.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { DefaultConfig } from './config.default'; - -export default () => { - const config: DefaultConfig = {}; - config.news = { - pageSize: 30, - }; - return config; -}; diff --git a/hackernews-async-ts/package.json b/hackernews-async-ts/package.json deleted file mode 100644 index 237a5b99..00000000 --- a/hackernews-async-ts/package.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "hackernews-async-ts", - "version": "1.0.0", - "description": "hackernews showcase using typescript && egg", - "private": true, - "egg": { - "typescript": true - }, - "scripts": { - "start": "egg-scripts start", - "dev": "egg-bin dev", - "debug": "egg-bin debug", - "test-local": "egg-bin test", - "test": "npm run lint -- --fix && npm run test-local", - "cov": "egg-bin cov", - "tsc": "tsc -p tsconfig.json", - "ci": "npm run lint && npm run cov && npm run tsc", - "lint": "eslint .", - "clean": "tsc -b --clean" - }, - "dependencies": { - "egg": "^3.11.0", - "egg-scripts": "^2.17.0", - "egg-view-nunjucks": "^2.3.0", - "moment": "^2.22.0" - }, - "devDependencies": { - "@eggjs/tsconfig": "^1.1.0", - "@types/cheerio": "^0.22.1", - "@types/mocha": "^10.0.1", - "cheerio": "^1.0.0-rc.2", - "egg-bin": "^5.9.0", - "egg-mock": "^5.5.0", - "eslint": "^8.31.0", - "eslint-config-egg": "^12.1.0", - "typescript": "^4.9.4" - }, - "engines": { - "node": ">=16.0.0" - } -} diff --git a/hackernews-tegg/app/controller/news.ts b/hackernews-tegg/app/controller/news.ts index 2bbd958d..4a359d49 100644 --- a/hackernews-tegg/app/controller/news.ts +++ b/hackernews-tegg/app/controller/news.ts @@ -6,7 +6,7 @@ export default class NewsController extends Controller { const pageSize = app.config.news.pageSize; const page = parseInt(ctx.query.page, 10) || 1; - const idList = await ctx.service.news.getTopStories(page); + const idList = await ctx.service.news.getTopStories(page) as number[]; // get itemInfo parallel const newsList = await Promise.all(idList.map(id => ctx.service.news.getItem(id))); @@ -15,7 +15,7 @@ export default class NewsController extends Controller { public async detail() { const { ctx } = this; - const id = ctx.params.id; + const id = ctx.params!.id as unknown as number; const newsInfo = await ctx.service.news.getItem(id); // get comment parallel const commentList = await Promise.all(newsInfo.kids.map(_id => ctx.service.news.getItem(_id))); @@ -24,7 +24,7 @@ export default class NewsController extends Controller { public async user() { const { ctx } = this; - const id = ctx.params.id; + const id = ctx.params!.id as unknown as number; const userInfo = await ctx.service.news.getUser(id); await ctx.render('news/user.tpl', { user: userInfo }); } diff --git a/hackernews-tegg/app/extend/filter.ts b/hackernews-tegg/app/extend/filter.ts index 80813fce..4bd566f6 100644 --- a/hackernews-tegg/app/extend/filter.ts +++ b/hackernews-tegg/app/extend/filter.ts @@ -1,9 +1,9 @@ import moment from 'moment'; -export function relativeTime(time) { - return moment(new Date(time * 1000)).fromNow(); +export function relativeTime(seconds: number) { + return moment(new Date(seconds * 1000)).fromNow(); } -export function domain(url) { +export function domain(url: string) { return url && url.split('/')[2]; } diff --git a/hackernews-tegg/app/service/News.ts b/hackernews-tegg/app/service/News.ts index ffef8d5c..f0663eaa 100644 --- a/hackernews-tegg/app/service/News.ts +++ b/hackernews-tegg/app/service/News.ts @@ -21,14 +21,14 @@ export class HackerNews extends Service { * @param api - Api name * @param opts - urllib options */ - public async request(api: string, opts?: any) { - const options = { + public async request(api: string, opts?: any): Promise { + const url = `${this.config.news.serverUrl}/${api}`; + const result = await this.ctx.httpclient.request(url, { dataType: 'json', - timeout: '30s', + timeout: 30000, ...opts, - }; - - const result = await this.ctx.curl(`${this.config.news.serverUrl}/${api}`, options); + }); + this.logger.info('request %s got data %j', url, result.data); return result.data; } @@ -61,7 +61,7 @@ export class HackerNews extends Service { * @param id - itemId */ public async getItem(id: number): Promise { - return await this.request(`item/${id}.json`); + return await this.request(`item/${id}.json`); } /** diff --git a/hackernews-tegg/config/config.default.ts b/hackernews-tegg/config/config.default.ts index 64dfc022..e86b8710 100644 --- a/hackernews-tegg/config/config.default.ts +++ b/hackernews-tegg/config/config.default.ts @@ -1,9 +1,10 @@ -import { EggAppConfig, PowerPartial } from 'egg'; -import * as fs from 'fs'; -import * as path from 'path'; +import fs from 'node:fs'; +import path from 'node:path'; + +import { defineConfigFactory, type EggAppConfig } from 'egg'; // for config.{env}.ts -export type DefaultConfig = PowerPartial; +export type DefaultConfig = Partial; // app special config scheme export interface BizConfig { @@ -14,29 +15,24 @@ export interface BizConfig { }; } -export default (appInfo: EggAppConfig) => { - const config = {} as PowerPartial & BizConfig; - - // app special config - config.sourceUrl = `https://github.com/eggjs/examples/tree/master/${appInfo.name}`; - config.news = { - pageSize: 30, - serverUrl: 'https://hacker-news.firebaseio.com/v0', - }; - - // override config from framework / plugin - config.keys = appInfo.name + '123456'; - - config.view = { - defaultViewEngine: 'nunjucks', - mapping: { - '.tpl': 'nunjucks', +export default defineConfigFactory(appInfo => { + return { + // override config from framework / plugin + keys: appInfo.name + '123456', + view: { + defaultViewEngine: 'nunjucks', + mapping: { + '.tpl': 'nunjucks', + }, + }, + siteFile: { + '/favicon.ico': fs.readFileSync(path.join(appInfo.baseDir, 'app/public/favicon.png')), + }, + // app special config + sourceUrl: `https://github.com/eggjs/examples/tree/master/${appInfo.name}`, + news: { + pageSize: 30, + serverUrl: 'https://hacker-news.firebaseio.com/v0', }, }; - - config.siteFile = { - '/favicon.ico': fs.readFileSync(path.join(appInfo.baseDir, 'app/public/favicon.png')), - }; - - return config; -}; +}); diff --git a/hackernews-tegg/config/config.local.ts b/hackernews-tegg/config/config.local.ts index c790a638..59a2a094 100644 --- a/hackernews-tegg/config/config.local.ts +++ b/hackernews-tegg/config/config.local.ts @@ -1,9 +1,7 @@ -import { DefaultConfig } from './config.default'; +import type { DefaultConfig } from './config.default.ts'; -export default () => { - const config: DefaultConfig = {}; - config.news = { +export default { + news: { pageSize: 20, - }; - return config; -}; + }, +} as DefaultConfig; diff --git a/hackernews-tegg/config/config.prod.ts b/hackernews-tegg/config/config.prod.ts index a1cb3447..03aa9b21 100644 --- a/hackernews-tegg/config/config.prod.ts +++ b/hackernews-tegg/config/config.prod.ts @@ -1,9 +1,7 @@ -import { DefaultConfig } from './config.default'; +import type { DefaultConfig } from './config.default.ts'; -export default () => { - const config: DefaultConfig = {}; - config.news = { +export default { + news: { pageSize: 30, - }; - return config; -}; + }, +} as DefaultConfig; diff --git a/hackernews-tegg/package.json b/hackernews-tegg/package.json index 237a5b99..59fb3943 100644 --- a/hackernews-tegg/package.json +++ b/hackernews-tegg/package.json @@ -1,15 +1,16 @@ { - "name": "hackernews-async-ts", + "name": "hackernews-tegg", "version": "1.0.0", - "description": "hackernews showcase using typescript && egg", + "description": "hackernews showcase using tegg", "private": true, + "type": "module", "egg": { "typescript": true }, "scripts": { - "start": "egg-scripts start", + "start": "eggctl start", + "stop": "eggctl stop", "dev": "egg-bin dev", - "debug": "egg-bin debug", "test-local": "egg-bin test", "test": "npm run lint -- --fix && npm run test-local", "cov": "egg-bin cov", @@ -19,23 +20,26 @@ "clean": "tsc -b --clean" }, "dependencies": { - "egg": "^3.11.0", - "egg-scripts": "^2.17.0", - "egg-view-nunjucks": "^2.3.0", - "moment": "^2.22.0" + "egg": "beta", + "@eggjs/scripts": "beta", + "@eggjs/tegg": "beta", + "@eggjs/tegg-config": "beta", + "@eggjs/tegg-controller-plugin": "beta", + "@eggjs/tegg-plugin": "beta", + "egg-view-nunjucks": "beta", + "moment": "^2.30.1" }, "devDependencies": { - "@eggjs/tsconfig": "^1.1.0", - "@types/cheerio": "^0.22.1", - "@types/mocha": "^10.0.1", - "cheerio": "^1.0.0-rc.2", - "egg-bin": "^5.9.0", - "egg-mock": "^5.5.0", - "eslint": "^8.31.0", - "eslint-config-egg": "^12.1.0", - "typescript": "^4.9.4" + "@eggjs/bin": "beta", + "@eggjs/mock": "beta", + "@eggjs/tsconfig": "beta", + "@types/mocha": "10", + "@types/node": "^24.8.1", + "eslint": "8", + "eslint-config-egg": "14", + "typescript": "5" }, "engines": { - "node": ">=16.0.0" + "node": ">=22.18.0" } } diff --git a/hackernews-tegg/test/app/controller/news.test.ts b/hackernews-tegg/test/app/controller/news.test.ts index ff2f9306..13b9c81c 100644 --- a/hackernews-tegg/test/app/controller/news.test.ts +++ b/hackernews-tegg/test/app/controller/news.test.ts @@ -1,19 +1,18 @@ -import { strict as assert } from 'assert'; -import * as cheerio from 'cheerio'; -import { app } from 'egg-mock/bootstrap'; +import assert from 'node:assert/strict'; + +// import cheerio from 'cheerio'; +import { app } from '@eggjs/mock/bootstrap'; describe('test/app/controller/news.test.ts', () => { it('should GET /news', async () => { const result = await app.httpRequest().get('/news').expect(200); - const $ = cheerio.load(result.text); - const listItem = $('.news-view .item'); - assert(listItem.length === app.config.news.pageSize); + assert.equal(result.text.length, app.config.news.pageSize); }); it('should GET /news/item/:id', async () => { await app.httpRequest() .get('/news/item/1') - // just a example, use regex to test part of dom string, but should be strong characteristic + // just a example, use regex to test part of dom string, but should be strong characteristic .expect(/\/news\/item\/1/) .expect(200); }); @@ -21,7 +20,7 @@ describe('test/app/controller/news.test.ts', () => { it('should GET /news/user/:id', async () => { await app.httpRequest() .get('/news/user/activatedgeek') - // just a example, use regex to test part of dom string, but should be strong characteristic + // just a example, use regex to test part of dom string, but should be strong characteristic .expect(/user:<\/span> activatedgeek/) .expect(200); }); diff --git a/hackernews-tegg/test/app/service/News.test.ts b/hackernews-tegg/test/app/service/News.test.ts index 1d922b04..ada73ba6 100644 --- a/hackernews-tegg/test/app/service/News.test.ts +++ b/hackernews-tegg/test/app/service/News.test.ts @@ -1,8 +1,9 @@ -import { strict as assert } from 'assert'; +import assert from 'node:assert/strict'; + import { Context } from 'egg'; -import { app } from 'egg-mock/bootstrap'; +import { app } from '@eggjs/mock/bootstrap'; -describe('test/app/service/News.test.js', () => { +describe('test/app/service/News.test.ts', () => { let ctx: Context; before(() => { @@ -11,11 +12,12 @@ describe('test/app/service/News.test.js', () => { it('getTopStories', async () => { const list = await ctx.service.news.getTopStories(); - assert(list.length === 30); + assert.equal(list.length, 30); }); it('getItem', async () => { const item = await ctx.service.news.getItem(1); + console.log(item); assert(item.id && item.title && item.url); }); }); diff --git a/hackernews-tegg/typings/app/controller/index.d.ts b/hackernews-tegg/typings/app/controller/index.d.ts new file mode 100644 index 00000000..d2c84939 --- /dev/null +++ b/hackernews-tegg/typings/app/controller/index.d.ts @@ -0,0 +1,12 @@ +// This file is created by egg-ts-helper@3.2.0 +// Do not modify this file!!!!!!!!! +/* eslint-disable */ + +import 'egg'; +import ExportNews from '../../../app/controller/news.js'; + +declare module 'egg' { + interface IController { + news: ExportNews; + } +} diff --git a/hackernews-tegg/typings/app/index.d.ts b/hackernews-tegg/typings/app/index.d.ts new file mode 100644 index 00000000..d772ae2b --- /dev/null +++ b/hackernews-tegg/typings/app/index.d.ts @@ -0,0 +1,7 @@ +// This file is created by egg-ts-helper@3.2.0 +// Do not modify this file!!!!!!!!! +/* eslint-disable */ + +import 'egg'; +export * from 'egg'; +export as namespace Egg; diff --git a/hackernews-tegg/typings/app/service/index.d.ts b/hackernews-tegg/typings/app/service/index.d.ts new file mode 100644 index 00000000..cacc4624 --- /dev/null +++ b/hackernews-tegg/typings/app/service/index.d.ts @@ -0,0 +1,16 @@ +// This file is created by egg-ts-helper@3.2.0 +// Do not modify this file!!!!!!!!! +/* eslint-disable */ + +import 'egg'; +type AnyClass = new (...args: any[]) => any; +type AnyFunc = (...args: any[]) => T; +type CanExportFunc = AnyFunc> | AnyFunc>; +type AutoInstanceType : T> = U extends AnyClass ? InstanceType : U; +import ExportNews from '../../../app/service/News.js'; + +declare module 'egg' { + interface IService { + news: AutoInstanceType; + } +} diff --git a/hackernews-async-ts/.eslintrc b/hackernews-typescript/.eslintrc similarity index 100% rename from hackernews-async-ts/.eslintrc rename to hackernews-typescript/.eslintrc diff --git a/hackernews-async-ts/.gitignore b/hackernews-typescript/.gitignore similarity index 100% rename from hackernews-async-ts/.gitignore rename to hackernews-typescript/.gitignore diff --git a/hackernews-async-ts/README.md b/hackernews-typescript/README.md similarity index 75% rename from hackernews-async-ts/README.md rename to hackernews-typescript/README.md index 62236e49..95f375c6 100644 --- a/hackernews-async-ts/README.md +++ b/hackernews-typescript/README.md @@ -1,6 +1,6 @@ -# hackernews-async-ts +# hackernews-typescript -[Hacker News](https://news.ycombinator.com/) showcase using typescript && egg +[Hacker News](https://news.ycombinator.com/) showcase using egg@4 and TypeScript ## QuickStart @@ -29,5 +29,6 @@ $ npm start ### Requirement -- Node.js 16.x -- Typescript 4.x +- Node.js 22.x +- Typescript 5.x +- egg@4.x diff --git a/hackernews-async-ts/app/controller/news.ts b/hackernews-typescript/app/controller/news.ts similarity index 84% rename from hackernews-async-ts/app/controller/news.ts rename to hackernews-typescript/app/controller/news.ts index 2bbd958d..4a359d49 100644 --- a/hackernews-async-ts/app/controller/news.ts +++ b/hackernews-typescript/app/controller/news.ts @@ -6,7 +6,7 @@ export default class NewsController extends Controller { const pageSize = app.config.news.pageSize; const page = parseInt(ctx.query.page, 10) || 1; - const idList = await ctx.service.news.getTopStories(page); + const idList = await ctx.service.news.getTopStories(page) as number[]; // get itemInfo parallel const newsList = await Promise.all(idList.map(id => ctx.service.news.getItem(id))); @@ -15,7 +15,7 @@ export default class NewsController extends Controller { public async detail() { const { ctx } = this; - const id = ctx.params.id; + const id = ctx.params!.id as unknown as number; const newsInfo = await ctx.service.news.getItem(id); // get comment parallel const commentList = await Promise.all(newsInfo.kids.map(_id => ctx.service.news.getItem(_id))); @@ -24,7 +24,7 @@ export default class NewsController extends Controller { public async user() { const { ctx } = this; - const id = ctx.params.id; + const id = ctx.params!.id as unknown as number; const userInfo = await ctx.service.news.getUser(id); await ctx.render('news/user.tpl', { user: userInfo }); } diff --git a/hackernews-typescript/app/extend/filter.ts b/hackernews-typescript/app/extend/filter.ts new file mode 100644 index 00000000..4bd566f6 --- /dev/null +++ b/hackernews-typescript/app/extend/filter.ts @@ -0,0 +1,9 @@ +import moment from 'moment'; + +export function relativeTime(seconds: number) { + return moment(new Date(seconds * 1000)).fromNow(); +} + +export function domain(url: string) { + return url && url.split('/')[2]; +} diff --git a/hackernews-async-ts-di/app/public/css/news.css b/hackernews-typescript/app/public/css/news.css similarity index 100% rename from hackernews-async-ts-di/app/public/css/news.css rename to hackernews-typescript/app/public/css/news.css diff --git a/hackernews-async-ts-di/app/public/favicon.png b/hackernews-typescript/app/public/favicon.png similarity index 100% rename from hackernews-async-ts-di/app/public/favicon.png rename to hackernews-typescript/app/public/favicon.png diff --git a/hackernews-async-ts/app/router.ts b/hackernews-typescript/app/router.ts similarity index 100% rename from hackernews-async-ts/app/router.ts rename to hackernews-typescript/app/router.ts diff --git a/hackernews-async-ts/app/service/News.ts b/hackernews-typescript/app/service/News.ts similarity index 80% rename from hackernews-async-ts/app/service/News.ts rename to hackernews-typescript/app/service/News.ts index ffef8d5c..f0663eaa 100644 --- a/hackernews-async-ts/app/service/News.ts +++ b/hackernews-typescript/app/service/News.ts @@ -21,14 +21,14 @@ export class HackerNews extends Service { * @param api - Api name * @param opts - urllib options */ - public async request(api: string, opts?: any) { - const options = { + public async request(api: string, opts?: any): Promise { + const url = `${this.config.news.serverUrl}/${api}`; + const result = await this.ctx.httpclient.request(url, { dataType: 'json', - timeout: '30s', + timeout: 30000, ...opts, - }; - - const result = await this.ctx.curl(`${this.config.news.serverUrl}/${api}`, options); + }); + this.logger.info('request %s got data %j', url, result.data); return result.data; } @@ -61,7 +61,7 @@ export class HackerNews extends Service { * @param id - itemId */ public async getItem(id: number): Promise { - return await this.request(`item/${id}.json`); + return await this.request(`item/${id}.json`); } /** diff --git a/hackernews-async-ts/app/view/layout/layout.tpl b/hackernews-typescript/app/view/layout/layout.tpl similarity index 100% rename from hackernews-async-ts/app/view/layout/layout.tpl rename to hackernews-typescript/app/view/layout/layout.tpl diff --git a/hackernews-async-ts-di/app/view/news/detail.tpl b/hackernews-typescript/app/view/news/detail.tpl similarity index 100% rename from hackernews-async-ts-di/app/view/news/detail.tpl rename to hackernews-typescript/app/view/news/detail.tpl diff --git a/hackernews-async-ts-di/app/view/news/item.tpl b/hackernews-typescript/app/view/news/item.tpl similarity index 100% rename from hackernews-async-ts-di/app/view/news/item.tpl rename to hackernews-typescript/app/view/news/item.tpl diff --git a/hackernews-async-ts-di/app/view/news/list.tpl b/hackernews-typescript/app/view/news/list.tpl similarity index 100% rename from hackernews-async-ts-di/app/view/news/list.tpl rename to hackernews-typescript/app/view/news/list.tpl diff --git a/hackernews-async-ts-di/app/view/news/user.tpl b/hackernews-typescript/app/view/news/user.tpl similarity index 100% rename from hackernews-async-ts-di/app/view/news/user.tpl rename to hackernews-typescript/app/view/news/user.tpl diff --git a/hackernews-typescript/config/config.default.ts b/hackernews-typescript/config/config.default.ts new file mode 100644 index 00000000..e86b8710 --- /dev/null +++ b/hackernews-typescript/config/config.default.ts @@ -0,0 +1,38 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +import { defineConfigFactory, type EggAppConfig } from 'egg'; + +// for config.{env}.ts +export type DefaultConfig = Partial; + +// app special config scheme +export interface BizConfig { + sourceUrl: string; + news: { + pageSize: number; + serverUrl: string; + }; +} + +export default defineConfigFactory(appInfo => { + return { + // override config from framework / plugin + keys: appInfo.name + '123456', + view: { + defaultViewEngine: 'nunjucks', + mapping: { + '.tpl': 'nunjucks', + }, + }, + siteFile: { + '/favicon.ico': fs.readFileSync(path.join(appInfo.baseDir, 'app/public/favicon.png')), + }, + // app special config + sourceUrl: `https://github.com/eggjs/examples/tree/master/${appInfo.name}`, + news: { + pageSize: 30, + serverUrl: 'https://hacker-news.firebaseio.com/v0', + }, + }; +}); diff --git a/hackernews-typescript/config/config.local.ts b/hackernews-typescript/config/config.local.ts new file mode 100644 index 00000000..59a2a094 --- /dev/null +++ b/hackernews-typescript/config/config.local.ts @@ -0,0 +1,7 @@ +import type { DefaultConfig } from './config.default.ts'; + +export default { + news: { + pageSize: 20, + }, +} as DefaultConfig; diff --git a/hackernews-typescript/config/config.prod.ts b/hackernews-typescript/config/config.prod.ts new file mode 100644 index 00000000..03aa9b21 --- /dev/null +++ b/hackernews-typescript/config/config.prod.ts @@ -0,0 +1,7 @@ +import type { DefaultConfig } from './config.default.ts'; + +export default { + news: { + pageSize: 30, + }, +} as DefaultConfig; diff --git a/hackernews-async-ts/config/plugin.ts b/hackernews-typescript/config/plugin.ts similarity index 100% rename from hackernews-async-ts/config/plugin.ts rename to hackernews-typescript/config/plugin.ts diff --git a/hackernews-typescript/package.json b/hackernews-typescript/package.json new file mode 100644 index 00000000..e081358a --- /dev/null +++ b/hackernews-typescript/package.json @@ -0,0 +1,45 @@ +{ + "name": "hackernews-typescript", + "version": "1.0.0", + "description": "hackernews showcase using egg@4 and TypeScript", + "private": true, + "type": "module", + "egg": { + "typescript": true + }, + "scripts": { + "start": "eggctl start", + "stop": "eggctl stop", + "dev": "egg-bin dev", + "test-local": "egg-bin test", + "test": "npm run lint -- --fix && npm run test-local", + "cov": "egg-bin cov", + "tsc": "tsc -p tsconfig.json", + "ci": "npm run lint && npm run cov && npm run tsc", + "lint": "eslint .", + "clean": "tsc -b --clean" + }, + "dependencies": { + "egg": "beta", + "@eggjs/scripts": "beta", + "@eggjs/tegg": "beta", + "@eggjs/tegg-config": "beta", + "@eggjs/tegg-controller-plugin": "beta", + "@eggjs/tegg-plugin": "beta", + "egg-view-nunjucks": "beta", + "moment": "^2.30.1" + }, + "devDependencies": { + "@eggjs/bin": "beta", + "@eggjs/mock": "beta", + "@eggjs/tsconfig": "beta", + "@types/mocha": "10", + "@types/node": "^24.8.1", + "eslint": "8", + "eslint-config-egg": "14", + "typescript": "5" + }, + "engines": { + "node": ">=22.18.0" + } +} diff --git a/hackernews-async-ts/test/app/controller/news.test.ts b/hackernews-typescript/test/app/controller/news.test.ts similarity index 54% rename from hackernews-async-ts/test/app/controller/news.test.ts rename to hackernews-typescript/test/app/controller/news.test.ts index ff2f9306..13b9c81c 100644 --- a/hackernews-async-ts/test/app/controller/news.test.ts +++ b/hackernews-typescript/test/app/controller/news.test.ts @@ -1,19 +1,18 @@ -import { strict as assert } from 'assert'; -import * as cheerio from 'cheerio'; -import { app } from 'egg-mock/bootstrap'; +import assert from 'node:assert/strict'; + +// import cheerio from 'cheerio'; +import { app } from '@eggjs/mock/bootstrap'; describe('test/app/controller/news.test.ts', () => { it('should GET /news', async () => { const result = await app.httpRequest().get('/news').expect(200); - const $ = cheerio.load(result.text); - const listItem = $('.news-view .item'); - assert(listItem.length === app.config.news.pageSize); + assert.equal(result.text.length, app.config.news.pageSize); }); it('should GET /news/item/:id', async () => { await app.httpRequest() .get('/news/item/1') - // just a example, use regex to test part of dom string, but should be strong characteristic + // just a example, use regex to test part of dom string, but should be strong characteristic .expect(/\/news\/item\/1/) .expect(200); }); @@ -21,7 +20,7 @@ describe('test/app/controller/news.test.ts', () => { it('should GET /news/user/:id', async () => { await app.httpRequest() .get('/news/user/activatedgeek') - // just a example, use regex to test part of dom string, but should be strong characteristic + // just a example, use regex to test part of dom string, but should be strong characteristic .expect(/user:<\/span> activatedgeek/) .expect(200); }); diff --git a/hackernews-async-ts/test/app/service/News.test.ts b/hackernews-typescript/test/app/service/News.test.ts similarity index 63% rename from hackernews-async-ts/test/app/service/News.test.ts rename to hackernews-typescript/test/app/service/News.test.ts index 1d922b04..ada73ba6 100644 --- a/hackernews-async-ts/test/app/service/News.test.ts +++ b/hackernews-typescript/test/app/service/News.test.ts @@ -1,8 +1,9 @@ -import { strict as assert } from 'assert'; +import assert from 'node:assert/strict'; + import { Context } from 'egg'; -import { app } from 'egg-mock/bootstrap'; +import { app } from '@eggjs/mock/bootstrap'; -describe('test/app/service/News.test.js', () => { +describe('test/app/service/News.test.ts', () => { let ctx: Context; before(() => { @@ -11,11 +12,12 @@ describe('test/app/service/News.test.js', () => { it('getTopStories', async () => { const list = await ctx.service.news.getTopStories(); - assert(list.length === 30); + assert.equal(list.length, 30); }); it('getItem', async () => { const item = await ctx.service.news.getItem(1); + console.log(item); assert(item.id && item.title && item.url); }); }); diff --git a/hackernews-async-ts/tsconfig.json b/hackernews-typescript/tsconfig.json similarity index 100% rename from hackernews-async-ts/tsconfig.json rename to hackernews-typescript/tsconfig.json diff --git a/hackernews-typescript/typings/app/controller/index.d.ts b/hackernews-typescript/typings/app/controller/index.d.ts new file mode 100644 index 00000000..d2c84939 --- /dev/null +++ b/hackernews-typescript/typings/app/controller/index.d.ts @@ -0,0 +1,12 @@ +// This file is created by egg-ts-helper@3.2.0 +// Do not modify this file!!!!!!!!! +/* eslint-disable */ + +import 'egg'; +import ExportNews from '../../../app/controller/news.js'; + +declare module 'egg' { + interface IController { + news: ExportNews; + } +} diff --git a/hackernews-typescript/typings/app/index.d.ts b/hackernews-typescript/typings/app/index.d.ts new file mode 100644 index 00000000..d772ae2b --- /dev/null +++ b/hackernews-typescript/typings/app/index.d.ts @@ -0,0 +1,7 @@ +// This file is created by egg-ts-helper@3.2.0 +// Do not modify this file!!!!!!!!! +/* eslint-disable */ + +import 'egg'; +export * from 'egg'; +export as namespace Egg; diff --git a/hackernews-typescript/typings/app/service/index.d.ts b/hackernews-typescript/typings/app/service/index.d.ts new file mode 100644 index 00000000..cacc4624 --- /dev/null +++ b/hackernews-typescript/typings/app/service/index.d.ts @@ -0,0 +1,16 @@ +// This file is created by egg-ts-helper@3.2.0 +// Do not modify this file!!!!!!!!! +/* eslint-disable */ + +import 'egg'; +type AnyClass = new (...args: any[]) => any; +type AnyFunc = (...args: any[]) => T; +type CanExportFunc = AnyFunc> | AnyFunc>; +type AutoInstanceType : T> = U extends AnyClass ? InstanceType : U; +import ExportNews from '../../../app/service/News.js'; + +declare module 'egg' { + interface IService { + news: AutoInstanceType; + } +} From 1e1b7237bbcbd150664feff8407ec48ca3c5385e Mon Sep 17 00:00:00 2001 From: MK Date: Sat, 18 Oct 2025 23:18:26 +0800 Subject: [PATCH 3/3] FIXUP --- hackernews-tegg/README.md | 8 ++++---- hackernews-tegg/app/public/favicon.png | Bin 3113 -> 0 bytes hackernews-tegg/app/router.ts | 2 +- hackernews-tegg/app/view/layout/layout.tpl | 6 +++--- hackernews-tegg/config/config.default.ts | 5 +---- hackernews-tegg/config/plugin.ts | 2 +- hackernews-tegg/package.json | 6 +++--- hackernews-tegg/test/app/controller/news.test.ts | 5 +++-- hackernews-typescript/README.md | 3 +-- hackernews-typescript/app/public/favicon.png | Bin 3113 -> 0 bytes hackernews-typescript/app/router.ts | 2 +- hackernews-typescript/app/view/layout/layout.tpl | 6 +++--- hackernews-typescript/config/config.default.ts | 5 +---- hackernews-typescript/config/plugin.ts | 2 +- hackernews-typescript/package.json | 8 ++++---- .../test/app/controller/news.test.ts | 5 +++-- 16 files changed, 30 insertions(+), 35 deletions(-) delete mode 100644 hackernews-tegg/app/public/favicon.png delete mode 100644 hackernews-typescript/app/public/favicon.png diff --git a/hackernews-tegg/README.md b/hackernews-tegg/README.md index 62236e49..a026f210 100644 --- a/hackernews-tegg/README.md +++ b/hackernews-tegg/README.md @@ -1,6 +1,6 @@ -# hackernews-async-ts +# hackernews-tegg -[Hacker News](https://news.ycombinator.com/) showcase using typescript && egg +[Hacker News](https://news.ycombinator.com/) showcase using tegg ## QuickStart @@ -29,5 +29,5 @@ $ npm start ### Requirement -- Node.js 16.x -- Typescript 4.x +- Node.js 22.x +- Typescript 5.x diff --git a/hackernews-tegg/app/public/favicon.png b/hackernews-tegg/app/public/favicon.png deleted file mode 100644 index 41d69cdc414faa424c7a12c783f8009f0fec2da8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3113 zcmZ8jZBLU|7;d3ZWE2q)Xlb$VFc1-~76A)Ipyf@V2$b@sWrea8kctAcQoxuknVnejbDQUT)ar7-EP*Yj9 z9H_(DMlcW`6@8>mG&rnVFzRn`q^8zL=rs2(-rBaTNlMDPCDm9~AePJ|Ko?7@A{E zi`C}YRTxiyj4oe-4u&7yh@1Y;d|MMjwuPXX>js!@iJ}f&J>-ganq^!Zo#2|*)Co8Wo4?lI0osj= zaM<92=EpbkU_HCeAAbCa1YcZx@U2+IhoP&|9t;8;Q%gBsEUV$`Un zX)cC8oZ%~E)%zPQ0VWlg-#?mU*#!Bk?8ZXBszxYP4ply8^x>Dz`@hNjt~dP=?ra0X zqcyf)9W>ZnF2McW`ABT=sr|YMgVobCRWOx_ki;u;oCVC?EUHVc`z?4fPt%C-WGejpaloW zLt-{;J`lhYckg2mx5hyMv*YCp0Us*>a8H_;Pt;)+&r>N{*x};*)lGh7!YU^)JIB|X zpavi%_6g6~WVsRMJCJqeho_M10(@@X5p!&%z;w;31jix)o-EeN264`SPO1i~hAk)! zIjpd_XMn%(8muOb$6K|??!j6X2k=7naH|0OmD||9KDm7z+Ff5CWzBUkO@hPW?ohw_ zK6w%>PN6>5!Q-yQ2jWntAz>~)?-GdF;LcsGb}%<4xWMZ*ntr+XimCzJS6+M$0hVr< z69Im*bo3RC&Dq!pIu7Ypj?#lp&C<#bEyNI?LmAu%3Dr}JIcHS z#EuowINstc%TqhlcIu~QiVE>{WoNuk-#f%2Cyc36D-*y*-74cv0wmQem)CaSKyhp8 z7Fy?Y)&nTrFsUku59Yd`{2r`-+n;J7iR;|H2=neu-4|m+EsGm+rAbV+&OaZ(ZRKk2f9-tbOvUg18ZpUS z;nV?v&x>{-2+EzVJ~YD{N-!&G4eB6-*E+y-h9GQYV4F8=jo3O*>(&Kc2!oJlX}ccG rwB+~k1jb$SF@uF0YU2~ecQe815J!PJps)e_<2Uqn2K+EP_$~Pl&{xhR diff --git a/hackernews-tegg/app/router.ts b/hackernews-tegg/app/router.ts index cead0f3c..4b6e8b23 100644 --- a/hackernews-tegg/app/router.ts +++ b/hackernews-tegg/app/router.ts @@ -3,7 +3,7 @@ import { Application } from 'egg'; export default (app: Application) => { const { controller, router } = app; - router.redirect('/', '/news'); + router.redirect('/', '/news', 302); router.get('/news', controller.news.list); router.get('/news/item/:id', controller.news.detail); router.get('/news/user/:id', controller.news.user); diff --git a/hackernews-tegg/app/view/layout/layout.tpl b/hackernews-tegg/app/view/layout/layout.tpl index aae4fb22..c86df617 100644 --- a/hackernews-tegg/app/view/layout/layout.tpl +++ b/hackernews-tegg/app/view/layout/layout.tpl @@ -4,16 +4,16 @@ - + {% block title %}egg - HackerNews{% endblock %}
{% block content %}{% endblock %} diff --git a/hackernews-tegg/config/config.default.ts b/hackernews-tegg/config/config.default.ts index e86b8710..575c1482 100644 --- a/hackernews-tegg/config/config.default.ts +++ b/hackernews-tegg/config/config.default.ts @@ -1,6 +1,3 @@ -import fs from 'node:fs'; -import path from 'node:path'; - import { defineConfigFactory, type EggAppConfig } from 'egg'; // for config.{env}.ts @@ -26,7 +23,7 @@ export default defineConfigFactory(appInfo => { }, }, siteFile: { - '/favicon.ico': fs.readFileSync(path.join(appInfo.baseDir, 'app/public/favicon.png')), + '/favicon.ico': 'https://eggjs.org/favicon.png', }, // app special config sourceUrl: `https://github.com/eggjs/examples/tree/master/${appInfo.name}`, diff --git a/hackernews-tegg/config/plugin.ts b/hackernews-tegg/config/plugin.ts index 34d8cfe0..8aafcfdf 100644 --- a/hackernews-tegg/config/plugin.ts +++ b/hackernews-tegg/config/plugin.ts @@ -1,6 +1,6 @@ export default { nunjucks: { enable: true, - package: 'egg-view-nunjucks', + package: '@eggjs/view-nunjucks', }, }; diff --git a/hackernews-tegg/package.json b/hackernews-tegg/package.json index 59fb3943..816649ba 100644 --- a/hackernews-tegg/package.json +++ b/hackernews-tegg/package.json @@ -20,13 +20,13 @@ "clean": "tsc -b --clean" }, "dependencies": { - "egg": "beta", "@eggjs/scripts": "beta", "@eggjs/tegg": "beta", "@eggjs/tegg-config": "beta", "@eggjs/tegg-controller-plugin": "beta", "@eggjs/tegg-plugin": "beta", - "egg-view-nunjucks": "beta", + "@eggjs/view-nunjucks": "beta", + "egg": "beta", "moment": "^2.30.1" }, "devDependencies": { @@ -34,7 +34,7 @@ "@eggjs/mock": "beta", "@eggjs/tsconfig": "beta", "@types/mocha": "10", - "@types/node": "^24.8.1", + "@types/node": "24", "eslint": "8", "eslint-config-egg": "14", "typescript": "5" diff --git a/hackernews-tegg/test/app/controller/news.test.ts b/hackernews-tegg/test/app/controller/news.test.ts index 13b9c81c..802bdbca 100644 --- a/hackernews-tegg/test/app/controller/news.test.ts +++ b/hackernews-tegg/test/app/controller/news.test.ts @@ -1,12 +1,13 @@ import assert from 'node:assert/strict'; -// import cheerio from 'cheerio'; import { app } from '@eggjs/mock/bootstrap'; describe('test/app/controller/news.test.ts', () => { it('should GET /news', async () => { const result = await app.httpRequest().get('/news').expect(200); - assert.equal(result.text.length, app.config.news.pageSize); + const m = result.text.match(/.*?<\/span>/g); + assert.ok(m); + assert.equal(m.length, app.config.news.pageSize); }); it('should GET /news/item/:id', async () => { diff --git a/hackernews-typescript/README.md b/hackernews-typescript/README.md index 95f375c6..8b04b73c 100644 --- a/hackernews-typescript/README.md +++ b/hackernews-typescript/README.md @@ -1,6 +1,6 @@ # hackernews-typescript -[Hacker News](https://news.ycombinator.com/) showcase using egg@4 and TypeScript +[Hacker News](https://news.ycombinator.com/) showcase using typescript && egg ## QuickStart @@ -31,4 +31,3 @@ $ npm start - Node.js 22.x - Typescript 5.x -- egg@4.x diff --git a/hackernews-typescript/app/public/favicon.png b/hackernews-typescript/app/public/favicon.png deleted file mode 100644 index 41d69cdc414faa424c7a12c783f8009f0fec2da8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3113 zcmZ8jZBLU|7;d3ZWE2q)Xlb$VFc1-~76A)Ipyf@V2$b@sWrea8kctAcQoxuknVnejbDQUT)ar7-EP*Yj9 z9H_(DMlcW`6@8>mG&rnVFzRn`q^8zL=rs2(-rBaTNlMDPCDm9~AePJ|Ko?7@A{E zi`C}YRTxiyj4oe-4u&7yh@1Y;d|MMjwuPXX>js!@iJ}f&J>-ganq^!Zo#2|*)Co8Wo4?lI0osj= zaM<92=EpbkU_HCeAAbCa1YcZx@U2+IhoP&|9t;8;Q%gBsEUV$`Un zX)cC8oZ%~E)%zPQ0VWlg-#?mU*#!Bk?8ZXBszxYP4ply8^x>Dz`@hNjt~dP=?ra0X zqcyf)9W>ZnF2McW`ABT=sr|YMgVobCRWOx_ki;u;oCVC?EUHVc`z?4fPt%C-WGejpaloW zLt-{;J`lhYckg2mx5hyMv*YCp0Us*>a8H_;Pt;)+&r>N{*x};*)lGh7!YU^)JIB|X zpavi%_6g6~WVsRMJCJqeho_M10(@@X5p!&%z;w;31jix)o-EeN264`SPO1i~hAk)! zIjpd_XMn%(8muOb$6K|??!j6X2k=7naH|0OmD||9KDm7z+Ff5CWzBUkO@hPW?ohw_ zK6w%>PN6>5!Q-yQ2jWntAz>~)?-GdF;LcsGb}%<4xWMZ*ntr+XimCzJS6+M$0hVr< z69Im*bo3RC&Dq!pIu7Ypj?#lp&C<#bEyNI?LmAu%3Dr}JIcHS z#EuowINstc%TqhlcIu~QiVE>{WoNuk-#f%2Cyc36D-*y*-74cv0wmQem)CaSKyhp8 z7Fy?Y)&nTrFsUku59Yd`{2r`-+n;J7iR;|H2=neu-4|m+EsGm+rAbV+&OaZ(ZRKk2f9-tbOvUg18ZpUS z;nV?v&x>{-2+EzVJ~YD{N-!&G4eB6-*E+y-h9GQYV4F8=jo3O*>(&Kc2!oJlX}ccG rwB+~k1jb$SF@uF0YU2~ecQe815J!PJps)e_<2Uqn2K+EP_$~Pl&{xhR diff --git a/hackernews-typescript/app/router.ts b/hackernews-typescript/app/router.ts index cead0f3c..4b6e8b23 100644 --- a/hackernews-typescript/app/router.ts +++ b/hackernews-typescript/app/router.ts @@ -3,7 +3,7 @@ import { Application } from 'egg'; export default (app: Application) => { const { controller, router } = app; - router.redirect('/', '/news'); + router.redirect('/', '/news', 302); router.get('/news', controller.news.list); router.get('/news/item/:id', controller.news.detail); router.get('/news/user/:id', controller.news.user); diff --git a/hackernews-typescript/app/view/layout/layout.tpl b/hackernews-typescript/app/view/layout/layout.tpl index aae4fb22..c86df617 100644 --- a/hackernews-typescript/app/view/layout/layout.tpl +++ b/hackernews-typescript/app/view/layout/layout.tpl @@ -4,16 +4,16 @@ - + {% block title %}egg - HackerNews{% endblock %}
{% block content %}{% endblock %} diff --git a/hackernews-typescript/config/config.default.ts b/hackernews-typescript/config/config.default.ts index e86b8710..575c1482 100644 --- a/hackernews-typescript/config/config.default.ts +++ b/hackernews-typescript/config/config.default.ts @@ -1,6 +1,3 @@ -import fs from 'node:fs'; -import path from 'node:path'; - import { defineConfigFactory, type EggAppConfig } from 'egg'; // for config.{env}.ts @@ -26,7 +23,7 @@ export default defineConfigFactory(appInfo => { }, }, siteFile: { - '/favicon.ico': fs.readFileSync(path.join(appInfo.baseDir, 'app/public/favicon.png')), + '/favicon.ico': 'https://eggjs.org/favicon.png', }, // app special config sourceUrl: `https://github.com/eggjs/examples/tree/master/${appInfo.name}`, diff --git a/hackernews-typescript/config/plugin.ts b/hackernews-typescript/config/plugin.ts index 34d8cfe0..8aafcfdf 100644 --- a/hackernews-typescript/config/plugin.ts +++ b/hackernews-typescript/config/plugin.ts @@ -1,6 +1,6 @@ export default { nunjucks: { enable: true, - package: 'egg-view-nunjucks', + package: '@eggjs/view-nunjucks', }, }; diff --git a/hackernews-typescript/package.json b/hackernews-typescript/package.json index e081358a..11798a53 100644 --- a/hackernews-typescript/package.json +++ b/hackernews-typescript/package.json @@ -1,7 +1,7 @@ { "name": "hackernews-typescript", "version": "1.0.0", - "description": "hackernews showcase using egg@4 and TypeScript", + "description": "hackernews showcase using typescript && egg", "private": true, "type": "module", "egg": { @@ -20,13 +20,13 @@ "clean": "tsc -b --clean" }, "dependencies": { - "egg": "beta", "@eggjs/scripts": "beta", "@eggjs/tegg": "beta", "@eggjs/tegg-config": "beta", "@eggjs/tegg-controller-plugin": "beta", "@eggjs/tegg-plugin": "beta", - "egg-view-nunjucks": "beta", + "@eggjs/view-nunjucks": "beta", + "egg": "beta", "moment": "^2.30.1" }, "devDependencies": { @@ -34,7 +34,7 @@ "@eggjs/mock": "beta", "@eggjs/tsconfig": "beta", "@types/mocha": "10", - "@types/node": "^24.8.1", + "@types/node": "24", "eslint": "8", "eslint-config-egg": "14", "typescript": "5" diff --git a/hackernews-typescript/test/app/controller/news.test.ts b/hackernews-typescript/test/app/controller/news.test.ts index 13b9c81c..802bdbca 100644 --- a/hackernews-typescript/test/app/controller/news.test.ts +++ b/hackernews-typescript/test/app/controller/news.test.ts @@ -1,12 +1,13 @@ import assert from 'node:assert/strict'; -// import cheerio from 'cheerio'; import { app } from '@eggjs/mock/bootstrap'; describe('test/app/controller/news.test.ts', () => { it('should GET /news', async () => { const result = await app.httpRequest().get('/news').expect(200); - assert.equal(result.text.length, app.config.news.pageSize); + const m = result.text.match(/.*?<\/span>/g); + assert.ok(m); + assert.equal(m.length, app.config.news.pageSize); }); it('should GET /news/item/:id', async () => {